Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6aa31147df | |||
| bc89452b5e | |||
| be8e8832f5 | |||
| 95682bb35f | |||
| fca849c1a5 | |||
| 80600be607 | |||
| 6b15d7b2a1 | |||
| ba45d09bdd | |||
| e71868dac6 | |||
| b41e3c7bb1 | |||
| 62a25726e8 | |||
| 1e739cfd3f |
0
.gitignore
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
26
README.md
Normal file → Executable file
26
README.md
Normal file → Executable file
@@ -3,7 +3,7 @@ Contributors: ronnygrobel
|
|||||||
Tags: verein, mitgliederverwaltung, parzellen, zaehler, abrechnung
|
Tags: verein, mitgliederverwaltung, parzellen, zaehler, abrechnung
|
||||||
Requires at least: 6.0
|
Requires at least: 6.0
|
||||||
Tested up to: 6.8
|
Tested up to: 6.8
|
||||||
Stable tag: 1.17.0
|
Stable tag: 1.17.8
|
||||||
Requires PHP: 7.2
|
Requires PHP: 7.2
|
||||||
License: GPLv2 or later
|
License: GPLv2 or later
|
||||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||||
@@ -43,6 +43,30 @@ Ja, insbesondere fuer Kleingartenvereine und deren Verwaltungsprozesse.
|
|||||||
|
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 1.17.8 =
|
||||||
|
Feat: Verbrauchsauswertung – Ablesung korrigieren (Datum, Zählerstand, Korrekturnotiz) und löschen direkt aus der Tabelle.
|
||||||
|
|
||||||
|
= 1.17.7 =
|
||||||
|
Feat: Inventarverwaltung – Gegenstände (Werkzeug etc.) erfassen, bearbeiten, löschen. Ausleihe und Rückgabe je Mitglied mit Notiz und Fälligkeitsdatum tracken. Export/Import integriert.
|
||||||
|
|
||||||
|
= 1.17.6 =
|
||||||
|
Feat: Jahresabrechnung kann festgeschrieben (gesperrt) werden. Alle Schreibzugriffe auf Kosten, Preise und Parzellenzuordnungen prüfen den Sperrstatus serverseitig.
|
||||||
|
|
||||||
|
= 1.17.5 =
|
||||||
|
Fix: Spaltenbreiten in Abrechnungstabellen angepasst für bessere Lesbarkeit langer Texte.
|
||||||
|
|
||||||
|
= 1.17.4 =
|
||||||
|
Fix: Checkbox "Verpflichtende Position" bei Kostenposten wird jetzt korrekt gespeichert wenn sie deaktiviert ist.
|
||||||
|
|
||||||
|
= 1.17.3 =
|
||||||
|
Verbesserung: Kostenübersicht zeigt jetzt direkt pro Kostenposten den Status Verpflichtend oder Manuell in einer eigenen Spalte an.
|
||||||
|
|
||||||
|
= 1.17.2 =
|
||||||
|
Fix: Manuelle Kostenpositionen auf der Jahresabrechnung einer Parzelle werden nach dem Hinzufügen jetzt sofort korrekt berücksichtigt. Pflichtpositionen ohne Einschränkung werden in der Seitenleiste als automatisch aktiv dargestellt.
|
||||||
|
|
||||||
|
= 1.17.1 =
|
||||||
|
Feat: is_mandatory Flag für Kostenpositionstypen - Kostenposten können jetzt als "verpflichtend" oder "manuell/optional" gekennzeichnet werden. Checkbox in der Kostenposten-Bearbeitung.
|
||||||
|
|
||||||
= 1.17.0 =
|
= 1.17.0 =
|
||||||
Parzellenspezifische Kostenpositionenzuweisung: Kostenposten können jetzt einzelnen Parzellen zugeordnet werden (z.B. Versicherung nur für bestimmte Parzellen). Editor auf der Jahresabrechnung Parzelle Seite mit übersichtlicher Liste und Hinzufügen/Entfernen-Funktionen. Zuordnungen werden in einer separaten Tabelle gespeichert und sind vollständig in Export/Import integriert.
|
Parzellenspezifische Kostenpositionenzuweisung: Kostenposten können jetzt einzelnen Parzellen zugeordnet werden (z.B. Versicherung nur für bestimmte Parzellen). Editor auf der Jahresabrechnung Parzelle Seite mit übersichtlicher Liste und Hinzufügen/Entfernen-Funktionen. Zuordnungen werden in einer separaten Tabelle gespeichert und sind vollständig in Export/Import integriert.
|
||||||
|
|
||||||
|
|||||||
0
assets/css/admin.css
Normal file → Executable file
0
assets/css/admin.css
Normal file → Executable file
0
assets/js/chat.js
Normal file → Executable file
0
assets/js/chat.js
Normal file → Executable file
0
includes/Activator.php
Normal file → Executable file
0
includes/Activator.php
Normal file → Executable file
721
includes/Admin/Admin.php
Normal file → Executable file
721
includes/Admin/Admin.php
Normal file → Executable file
@@ -14,6 +14,7 @@ use KGV\VereinManager\Repositories\ChatRepository;
|
|||||||
use KGV\VereinManager\Repositories\CostRepository;
|
use KGV\VereinManager\Repositories\CostRepository;
|
||||||
use KGV\VereinManager\Repositories\MeterReadingRepository;
|
use KGV\VereinManager\Repositories\MeterReadingRepository;
|
||||||
use KGV\VereinManager\Repositories\MeterRepository;
|
use KGV\VereinManager\Repositories\MeterRepository;
|
||||||
|
use KGV\VereinManager\Repositories\InventoryRepository;
|
||||||
use KGV\VereinManager\Repositories\ParcelRepository;
|
use KGV\VereinManager\Repositories\ParcelRepository;
|
||||||
use KGV\VereinManager\Repositories\SectionRepository;
|
use KGV\VereinManager\Repositories\SectionRepository;
|
||||||
use KGV\VereinManager\Repositories\TenantRepository;
|
use KGV\VereinManager\Repositories\TenantRepository;
|
||||||
@@ -41,6 +42,7 @@ class Admin {
|
|||||||
private $parcels;
|
private $parcels;
|
||||||
private $meters;
|
private $meters;
|
||||||
private $readings;
|
private $readings;
|
||||||
|
private $inventory;
|
||||||
private $tenants;
|
private $tenants;
|
||||||
private $assignments;
|
private $assignments;
|
||||||
private $chat;
|
private $chat;
|
||||||
@@ -58,6 +60,7 @@ class Admin {
|
|||||||
$this->parcels = new ParcelRepository();
|
$this->parcels = new ParcelRepository();
|
||||||
$this->meters = new MeterRepository();
|
$this->meters = new MeterRepository();
|
||||||
$this->readings = new MeterReadingRepository();
|
$this->readings = new MeterReadingRepository();
|
||||||
|
$this->inventory = new InventoryRepository();
|
||||||
$this->tenants = new TenantRepository();
|
$this->tenants = new TenantRepository();
|
||||||
$this->assignments = new AssignmentRepository();
|
$this->assignments = new AssignmentRepository();
|
||||||
$this->chat = new ChatRepository();
|
$this->chat = new ChatRepository();
|
||||||
@@ -106,6 +109,7 @@ class Admin {
|
|||||||
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', __( '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', __( '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', __( 'Zähler', KGVVM_TEXT_DOMAIN ), __( 'Zähler', KGVVM_TEXT_DOMAIN ), 'edit_zaehler', 'kgvvm-zaehler', array( $this, 'render_meters_page' ) );
|
||||||
|
add_submenu_page( 'kgvvm-dashboard', __( 'Inventar', KGVVM_TEXT_DOMAIN ), __( 'Inventar', KGVVM_TEXT_DOMAIN ), $cap, 'kgvvm-inventory', array( $this, 'render_inventory_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', __( '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', __( 'Kosten', KGVVM_TEXT_DOMAIN ), __( 'Kosten', KGVVM_TEXT_DOMAIN ), $cap, 'kgvvm-costs', array( $this, 'render_costs_page' ) );
|
||||||
add_submenu_page( 'kgvvm-dashboard', __( 'Arbeitsstunden', KGVVM_TEXT_DOMAIN ), __( 'Arbeitsstunden', KGVVM_TEXT_DOMAIN ), $cap, 'kgvvm-arbeit', array( $this, 'render_work_page' ) );
|
add_submenu_page( 'kgvvm-dashboard', __( 'Arbeitsstunden', KGVVM_TEXT_DOMAIN ), __( 'Arbeitsstunden', KGVVM_TEXT_DOMAIN ), $cap, 'kgvvm-arbeit', array( $this, 'render_work_page' ) );
|
||||||
@@ -234,6 +238,18 @@ class Admin {
|
|||||||
case 'save_meter_reading':
|
case 'save_meter_reading':
|
||||||
$this->save_meter_reading();
|
$this->save_meter_reading();
|
||||||
break;
|
break;
|
||||||
|
case 'correct_meter_reading':
|
||||||
|
$this->correct_meter_reading();
|
||||||
|
break;
|
||||||
|
case 'save_inventory_item':
|
||||||
|
$this->save_inventory_item();
|
||||||
|
break;
|
||||||
|
case 'borrow_inventory_item':
|
||||||
|
$this->borrow_inventory_item();
|
||||||
|
break;
|
||||||
|
case 'return_inventory_loan':
|
||||||
|
$this->return_inventory_loan();
|
||||||
|
break;
|
||||||
case 'save_settings':
|
case 'save_settings':
|
||||||
$this->save_settings();
|
$this->save_settings();
|
||||||
break;
|
break;
|
||||||
@@ -252,6 +268,9 @@ class Admin {
|
|||||||
case 'toggle_parcel_cost_assignment':
|
case 'toggle_parcel_cost_assignment':
|
||||||
$this->toggle_parcel_cost_assignment();
|
$this->toggle_parcel_cost_assignment();
|
||||||
break;
|
break;
|
||||||
|
case 'set_statement_lock':
|
||||||
|
$this->set_statement_lock();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,6 +315,25 @@ class Admin {
|
|||||||
$this->meters->delete( $id );
|
$this->meters->delete( $id );
|
||||||
$this->redirect_with_notice( 'kgvvm-zaehler', 'success', __( 'Zähler wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
|
$this->redirect_with_notice( 'kgvvm-zaehler', 'success', __( 'Zähler wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
|
||||||
break;
|
break;
|
||||||
|
case 'delete_meter_reading':
|
||||||
|
$this->require_cap( 'manage_kleingarten' );
|
||||||
|
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_meter_reading_' . $id ) ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-consumption', 'error', __( 'Der Löschvorgang wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
$this->readings->delete( $id );
|
||||||
|
$this->redirect_with_notice( 'kgvvm-consumption', 'success', __( 'Die Ablesung wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
break;
|
||||||
|
case 'delete_inventory_item':
|
||||||
|
$this->require_cap( 'manage_kleingarten' );
|
||||||
|
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_inventory_item_' . $id ) ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'error', __( 'Der Löschvorgang für den Inventargegenstand wurde aus Sicherheitsgründen abgebrochen.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
if ( $this->inventory->has_open_loans( $id ) ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'error', __( 'Der Inventargegenstand hat noch offene Ausleihen und kann nicht gelöscht werden.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
$this->inventory->delete( $id );
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'success', __( 'Inventargegenstand wurde gelöscht.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
break;
|
||||||
case 'delete_tenant':
|
case 'delete_tenant':
|
||||||
$this->require_cap( 'edit_paechter' );
|
$this->require_cap( 'edit_paechter' );
|
||||||
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_tenant_' . $id ) ) {
|
if ( ! wp_verify_nonce( $nonce, 'kgvvm_delete_tenant_' . $id ) ) {
|
||||||
@@ -316,6 +354,9 @@ class Admin {
|
|||||||
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Der gewünschte Kostenposten wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
|
$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;
|
$year = (int) $cost->entry_year;
|
||||||
|
if ( $this->costs->is_statement_locked( $year ) ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Die Jahresabrechnung ist festgeschrieben. Änderungen sind erst nach erneuter Freigabe möglich.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
|
||||||
|
}
|
||||||
$this->costs->delete_assignments_for_entry( $id );
|
$this->costs->delete_assignments_for_entry( $id );
|
||||||
$this->costs->delete( $id );
|
$this->costs->delete( $id );
|
||||||
$this->redirect_with_notice( 'kgvvm-costs', 'success', __( 'Kostenposten wurde gelöscht.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
|
$this->redirect_with_notice( 'kgvvm-costs', 'success', __( 'Kostenposten wurde gelöscht.', KGVVM_TEXT_DOMAIN ), array( 'year' => $year ) );
|
||||||
@@ -660,6 +701,52 @@ class Admin {
|
|||||||
$this->redirect_with_notice( $return_page, 'success', __( 'Die Ablesung wurde gespeichert.', KGVVM_TEXT_DOMAIN ), $return_args );
|
$this->redirect_with_notice( $return_page, 'success', __( 'Die Ablesung wurde gespeichert.', KGVVM_TEXT_DOMAIN ), $return_args );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct an existing meter reading (admin only).
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function correct_meter_reading() {
|
||||||
|
$this->require_cap( 'manage_kleingarten' );
|
||||||
|
check_admin_referer( 'kgvvm_correct_meter_reading' );
|
||||||
|
|
||||||
|
$id = absint( isset( $_POST['reading_id'] ) ? $_POST['reading_id'] : 0 );
|
||||||
|
$value = isset( $_POST['reading_value'] ) ? (float) wp_unslash( $_POST['reading_value'] ) : null;
|
||||||
|
$date = isset( $_POST['reading_date'] ) ? sanitize_text_field( wp_unslash( $_POST['reading_date'] ) ) : '';
|
||||||
|
$note = isset( $_POST['note'] ) ? sanitize_textarea_field( wp_unslash( $_POST['note'] ) ) : '';
|
||||||
|
|
||||||
|
$return_args = array_filter(
|
||||||
|
array(
|
||||||
|
'section_id' => absint( isset( $_POST['section_id'] ) ? $_POST['section_id'] : 0 ),
|
||||||
|
'date_from' => sanitize_text_field( wp_unslash( isset( $_POST['date_from'] ) ? $_POST['date_from'] : '' ) ),
|
||||||
|
'date_to' => sanitize_text_field( wp_unslash( isset( $_POST['date_to'] ) ? $_POST['date_to'] : '' ) ),
|
||||||
|
'order' => sanitize_key( wp_unslash( isset( $_POST['order'] ) ? $_POST['order'] : 'DESC' ) ),
|
||||||
|
),
|
||||||
|
static function( $v ) { return '' !== $v && 0 !== $v; }
|
||||||
|
);
|
||||||
|
|
||||||
|
$reading = $id ? $this->readings->find( $id ) : null;
|
||||||
|
|
||||||
|
if ( ! $reading ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-consumption', 'error', __( 'Die Ablesung wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ), $return_args );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( null === $value || '' === $date ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-consumption', 'error', __( 'Bitte Zählerstand und Datum angeben.', KGVVM_TEXT_DOMAIN ), $return_args );
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->readings->update_reading(
|
||||||
|
$id,
|
||||||
|
array(
|
||||||
|
'reading_value' => $value,
|
||||||
|
'reading_date' => $date,
|
||||||
|
'note' => $note,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->redirect_with_notice( 'kgvvm-consumption', 'success', __( 'Die Ablesung wurde korrigiert.', KGVVM_TEXT_DOMAIN ), $return_args );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save general settings.
|
* Save general settings.
|
||||||
*
|
*
|
||||||
@@ -794,6 +881,10 @@ class Admin {
|
|||||||
$this->redirect_with_notice( 'kgvvm-costs', 'error', $errors->get_error_message(), array( 'year' => $data['entry_year'] ) );
|
$this->redirect_with_notice( 'kgvvm-costs', 'error', $errors->get_error_message(), array( 'year' => $data['entry_year'] ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( $this->costs->is_statement_locked( (int) $data['entry_year'] ) ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Die Jahresabrechnung ist festgeschrieben. Änderungen sind erst nach erneuter Freigabe möglich.', KGVVM_TEXT_DOMAIN ), array( 'year' => $data['entry_year'] ) );
|
||||||
|
}
|
||||||
|
|
||||||
if ( ! $this->costs->save_section_prices( $data ) ) {
|
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', 'error', __( 'Die Preise für diese Sparte konnten nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ), array( 'year' => $data['entry_year'] ) );
|
||||||
}
|
}
|
||||||
@@ -813,11 +904,20 @@ class Admin {
|
|||||||
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
|
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
|
||||||
$data = $this->validator->sanitize_cost_entry( $_POST );
|
$data = $this->validator->sanitize_cost_entry( $_POST );
|
||||||
$errors = $this->validator->validate_cost_entry( $data );
|
$errors = $this->validator->validate_cost_entry( $data );
|
||||||
|
$existing = $id > 0 ? $this->costs->find( $id ) : null;
|
||||||
|
|
||||||
if ( $errors->has_errors() ) {
|
if ( $errors->has_errors() ) {
|
||||||
$this->redirect_with_notice( 'kgvvm-costs', 'error', $errors->get_error_message(), array( 'year' => $data['entry_year'], 'id' => $id ) );
|
$this->redirect_with_notice( 'kgvvm-costs', 'error', $errors->get_error_message(), array( 'year' => $data['entry_year'], 'id' => $id ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( $existing && $this->costs->is_statement_locked( (int) $existing->entry_year ) ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Die Jahresabrechnung ist festgeschrieben. Änderungen sind erst nach erneuter Freigabe möglich.', KGVVM_TEXT_DOMAIN ), array( 'year' => (int) $existing->entry_year, 'id' => $id ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $this->costs->is_statement_locked( (int) $data['entry_year'] ) ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Die Jahresabrechnung ist festgeschrieben. Änderungen sind erst nach erneuter Freigabe möglich.', KGVVM_TEXT_DOMAIN ), array( 'year' => $data['entry_year'], 'id' => $id ) );
|
||||||
|
}
|
||||||
|
|
||||||
$active_parcels = array_values(
|
$active_parcels = array_values(
|
||||||
array_filter(
|
array_filter(
|
||||||
$this->parcels->search(),
|
$this->parcels->search(),
|
||||||
@@ -857,6 +957,10 @@ class Admin {
|
|||||||
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Ungültige Anfrage.', KGVVM_TEXT_DOMAIN ), array( 'view' => 'statement', 'statement_type' => 'parcel', 'subject_id' => $parcel_id, 'year' => $year ) );
|
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Ungültige Anfrage.', KGVVM_TEXT_DOMAIN ), array( 'view' => 'statement', 'statement_type' => 'parcel', 'subject_id' => $parcel_id, 'year' => $year ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( $this->costs->is_statement_locked( $year ) ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Die Jahresabrechnung ist festgeschrieben. Änderungen sind erst nach erneuter Freigabe möglich.', KGVVM_TEXT_DOMAIN ), array( 'view' => 'statement', 'statement_type' => 'parcel', 'subject_id' => $parcel_id, 'year' => $year ) );
|
||||||
|
}
|
||||||
|
|
||||||
if ( 'add' === $mode ) {
|
if ( 'add' === $mode ) {
|
||||||
$this->costs->assign_to_parcel( $parcel_id, $cost_entry_id );
|
$this->costs->assign_to_parcel( $parcel_id, $cost_entry_id );
|
||||||
} else {
|
} else {
|
||||||
@@ -867,6 +971,401 @@ class Admin {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock or unlock annual statement changes for one year.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function set_statement_lock() {
|
||||||
|
$this->require_cap( 'manage_kleingarten' );
|
||||||
|
$year = absint( isset( $_POST['year'] ) ? $_POST['year'] : current_time( 'Y' ) );
|
||||||
|
$statement_type = isset( $_POST['statement_type'] ) ? sanitize_key( wp_unslash( $_POST['statement_type'] ) ) : 'parcel';
|
||||||
|
$subject_id = absint( isset( $_POST['subject_id'] ) ? $_POST['subject_id'] : 0 );
|
||||||
|
$mode = isset( $_POST['lock_mode'] ) ? sanitize_key( wp_unslash( $_POST['lock_mode'] ) ) : '';
|
||||||
|
|
||||||
|
check_admin_referer( 'kgvvm_set_statement_lock_' . $year );
|
||||||
|
|
||||||
|
if ( $year < 1 || ! in_array( $mode, array( 'lock', 'unlock' ), true ) ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Ungültige Anfrage.', KGVVM_TEXT_DOMAIN ), array( 'view' => 'statement', 'statement_type' => $statement_type, 'subject_id' => $subject_id, 'year' => $year ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_locked = 'lock' === $mode;
|
||||||
|
$saved = $this->costs->set_statement_lock( $year, $is_locked, get_current_user_id() );
|
||||||
|
|
||||||
|
if ( ! $saved ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-costs', 'error', __( 'Der Sperrstatus der Jahresabrechnung konnte nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ), array( 'view' => 'statement', 'statement_type' => $statement_type, 'subject_id' => $subject_id, 'year' => $year ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect_with_notice(
|
||||||
|
'kgvvm-costs',
|
||||||
|
'success',
|
||||||
|
$is_locked ? __( 'Jahresabrechnung wurde festgeschrieben.', KGVVM_TEXT_DOMAIN ) : __( 'Jahresabrechnung wurde erneut freigegeben.', KGVVM_TEXT_DOMAIN ),
|
||||||
|
array( 'view' => 'statement', 'statement_type' => $statement_type, 'subject_id' => $subject_id, 'year' => $year )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save one inventory item.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function save_inventory_item() {
|
||||||
|
$this->require_cap( 'manage_kleingarten' );
|
||||||
|
check_admin_referer( 'kgvvm_save_inventory_item' );
|
||||||
|
|
||||||
|
$id = absint( isset( $_POST['id'] ) ? $_POST['id'] : 0 );
|
||||||
|
$data = $this->validator->sanitize_inventory_item( $_POST );
|
||||||
|
$errors = $this->validator->validate_inventory_item( $data );
|
||||||
|
$existing = $id > 0 ? $this->inventory->find( $id ) : null;
|
||||||
|
|
||||||
|
if ( $existing ) {
|
||||||
|
$borrowed_qty = $this->inventory->get_open_borrowed_quantity( $id );
|
||||||
|
|
||||||
|
if ( (int) $data['total_quantity'] < $borrowed_qty ) {
|
||||||
|
$errors->add( 'inventory_total_too_low', __( 'Die Gesamtmenge ist kleiner als aktuell ausgeliehen. Bitte zuerst Rückgaben erfassen.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $errors->has_errors() ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'error', $errors->get_error_message(), array( 'id' => $id ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$saved = $this->inventory->save_item( $data, $id );
|
||||||
|
|
||||||
|
if ( ! $saved ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'error', __( 'Der Inventargegenstand konnte nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ), array( 'id' => $id ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'success', __( 'Inventargegenstand wurde gespeichert.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Borrow inventory item for one member.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function borrow_inventory_item() {
|
||||||
|
$this->require_cap( 'manage_kleingarten' );
|
||||||
|
check_admin_referer( 'kgvvm_borrow_inventory_item' );
|
||||||
|
|
||||||
|
$data = $this->validator->sanitize_inventory_loan( $_POST );
|
||||||
|
$errors = $this->validator->validate_inventory_loan( $data );
|
||||||
|
|
||||||
|
$item = $this->inventory->find( $data['item_id'] );
|
||||||
|
|
||||||
|
if ( ! $item ) {
|
||||||
|
$errors->add( 'inventory_item_missing', __( 'Der gewählte Inventargegenstand wurde nicht gefunden.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
} elseif ( ! (bool) $item->is_active ) {
|
||||||
|
$errors->add( 'inventory_item_inactive', __( 'Der gewählte Inventargegenstand ist inaktiv.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
} elseif ( (int) $data['quantity'] > (int) $item->available_quantity ) {
|
||||||
|
$errors->add( 'inventory_item_not_available', __( 'Die gewünschte Menge ist nicht verfügbar.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $errors->has_errors() ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'error', $errors->get_error_message() );
|
||||||
|
}
|
||||||
|
|
||||||
|
$saved = $this->inventory->borrow_item( $data['item_id'], $data['user_id'], $data['quantity'], $data['due_date'], $data['note'] );
|
||||||
|
|
||||||
|
if ( ! $saved ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'error', __( 'Die Ausleihe konnte nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'success', __( 'Ausleihe wurde erfasst.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return one inventory loan.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function return_inventory_loan() {
|
||||||
|
$this->require_cap( 'manage_kleingarten' );
|
||||||
|
check_admin_referer( 'kgvvm_return_inventory_loan' );
|
||||||
|
|
||||||
|
$loan_id = absint( isset( $_POST['loan_id'] ) ? $_POST['loan_id'] : 0 );
|
||||||
|
$return_note = sanitize_textarea_field( wp_unslash( isset( $_POST['return_note'] ) ? $_POST['return_note'] : '' ) );
|
||||||
|
|
||||||
|
if ( $loan_id < 1 ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'error', __( 'Ungültige Anfrage.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$saved = $this->inventory->return_loan( $loan_id, $return_note );
|
||||||
|
|
||||||
|
if ( ! $saved ) {
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'error', __( 'Die Rückgabe konnte nicht gespeichert werden.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->redirect_with_notice( 'kgvvm-inventory', 'success', __( 'Rückgabe wurde erfasst.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render inventory management page.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function render_inventory_page() {
|
||||||
|
$this->require_cap( 'manage_kleingarten' );
|
||||||
|
|
||||||
|
$search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : '';
|
||||||
|
$edit_id = absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 );
|
||||||
|
$item = $edit_id > 0 ? $this->inventory->find( $edit_id ) : null;
|
||||||
|
|
||||||
|
$items = $this->inventory->search_items( array( 's' => $search, 'orderby' => isset( $_GET['orderby'] ) ? sanitize_key( wp_unslash( $_GET['orderby'] ) ) : 'name', 'order' => isset( $_GET['order'] ) ? sanitize_key( wp_unslash( $_GET['order'] ) ) : 'ASC' ) );
|
||||||
|
$open_loans = $this->inventory->get_open_loans();
|
||||||
|
$recent_loans = $this->inventory->get_recent_loans( 20 );
|
||||||
|
|
||||||
|
$member_query = new \WP_User_Query(
|
||||||
|
array(
|
||||||
|
'role' => Roles::MEMBER_ROLE,
|
||||||
|
'fields' => array( 'ID', 'display_name', 'user_email' ),
|
||||||
|
'orderby' => 'display_name',
|
||||||
|
'order' => 'ASC',
|
||||||
|
'number' => 2000,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
$members = (array) $member_query->get_results();
|
||||||
|
|
||||||
|
if ( empty( $members ) ) {
|
||||||
|
$members = (array) $this->assignments->get_member_users();
|
||||||
|
}
|
||||||
|
|
||||||
|
$summary_total_items = count( $items );
|
||||||
|
$summary_available_total = 0;
|
||||||
|
$summary_open_total = count( $open_loans );
|
||||||
|
|
||||||
|
foreach ( $items as $row ) {
|
||||||
|
$summary_available_total += max( 0, (int) $row->available_quantity );
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class='wrap'>
|
||||||
|
<h1 class='wp-heading-inline'><?php echo esc_html__( 'Inventar', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||||||
|
<?php if ( $item ) : ?>
|
||||||
|
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-inventory' ) ); ?>' class='page-title-action'><?php echo esc_html__( 'Neuer Gegenstand', KGVVM_TEXT_DOMAIN ); ?></a>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php $this->render_notice(); ?>
|
||||||
|
|
||||||
|
<form method='get' class='kgvvm-toolbar'>
|
||||||
|
<input type='hidden' name='page' value='kgvvm-inventory' />
|
||||||
|
<div class='kgvvm-filters'>
|
||||||
|
<input type='search' name='s' value='<?php echo esc_attr( $search ); ?>' placeholder='<?php echo esc_attr__( 'Inventar 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( number_format_i18n( $summary_total_items, 0 ) ); ?></h2>
|
||||||
|
<p><?php echo esc_html__( 'Inventargegenstände', KGVVM_TEXT_DOMAIN ); ?></p>
|
||||||
|
</div>
|
||||||
|
<div class='kgvvm-card'>
|
||||||
|
<h2><?php echo esc_html( number_format_i18n( $summary_available_total, 0 ) ); ?></h2>
|
||||||
|
<p><?php echo esc_html__( 'Verfügbar gesamt', KGVVM_TEXT_DOMAIN ); ?></p>
|
||||||
|
</div>
|
||||||
|
<div class='kgvvm-card'>
|
||||||
|
<h2><?php echo esc_html( number_format_i18n( $summary_open_total, 0 ) ); ?></h2>
|
||||||
|
<p><?php echo esc_html__( 'Offene Ausleihen', KGVVM_TEXT_DOMAIN ); ?></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='kgvvm-grid'>
|
||||||
|
<div class='kgvvm-card'>
|
||||||
|
<h2><?php echo esc_html( $item ? __( 'Gegenstand bearbeiten', KGVVM_TEXT_DOMAIN ) : __( 'Gegenstand anlegen', KGVVM_TEXT_DOMAIN ) ); ?></h2>
|
||||||
|
<form method='post'>
|
||||||
|
<?php wp_nonce_field( 'kgvvm_save_inventory_item' ); ?>
|
||||||
|
<input type='hidden' name='kgvvm_action' value='save_inventory_item' />
|
||||||
|
<input type='hidden' name='id' value='<?php echo esc_attr( $item ? $item->id : 0 ); ?>' />
|
||||||
|
<table class='form-table kgvvm-form-table'>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><label for='kgvvm-item-name'><?php echo esc_html__( 'Name', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td><input name='name' id='kgvvm-item-name' type='text' class='regular-text' required value='<?php echo esc_attr( $item ? $item->name : '' ); ?>' /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><label for='kgvvm-item-total'><?php echo esc_html__( 'Gesamtmenge', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td><input name='total_quantity' id='kgvvm-item-total' type='number' min='0' required value='<?php echo esc_attr( $item ? (int) $item->total_quantity : 1 ); ?>' /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><label for='kgvvm-item-available'><?php echo esc_html__( 'Verfügbar', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td><input name='available_quantity' id='kgvvm-item-available' type='number' min='0' required value='<?php echo esc_attr( $item ? (int) $item->available_quantity : 1 ); ?>' /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><label for='kgvvm-item-location'><?php echo esc_html__( 'Lagerort', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td><input name='storage_location' id='kgvvm-item-location' type='text' class='regular-text' value='<?php echo esc_attr( $item ? $item->storage_location : '' ); ?>' /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><label for='kgvvm-item-description'><?php echo esc_html__( 'Beschreibung', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td><textarea name='description' id='kgvvm-item-description' rows='3' class='large-text'><?php echo esc_textarea( $item ? $item->description : '' ); ?></textarea></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><?php echo esc_html__( 'Status', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<td>
|
||||||
|
<label><input type='hidden' name='is_active' value='0' /><input type='checkbox' name='is_active' value='1' <?php checked( $item ? (bool) $item->is_active : true ); ?> /> <?php echo esc_html__( 'Aktiv', KGVVM_TEXT_DOMAIN ); ?></label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php submit_button( $item ? __( 'Gegenstand aktualisieren', KGVVM_TEXT_DOMAIN ) : __( 'Gegenstand speichern', KGVVM_TEXT_DOMAIN ) ); ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='kgvvm-card'>
|
||||||
|
<h2><?php echo esc_html__( 'Ausleihe erfassen', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||||||
|
<form method='post'>
|
||||||
|
<?php wp_nonce_field( 'kgvvm_borrow_inventory_item' ); ?>
|
||||||
|
<input type='hidden' name='kgvvm_action' value='borrow_inventory_item' />
|
||||||
|
<table class='form-table kgvvm-form-table'>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><label for='kgvvm-loan-item'><?php echo esc_html__( 'Gegenstand', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<select name='item_id' id='kgvvm-loan-item' required>
|
||||||
|
<option value=''><?php echo esc_html__( 'Bitte wählen', KGVVM_TEXT_DOMAIN ); ?></option>
|
||||||
|
<?php foreach ( $items as $inventory_item ) : ?>
|
||||||
|
<?php if ( ! (bool) $inventory_item->is_active ) : ?>
|
||||||
|
<?php continue; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
<option value='<?php echo esc_attr( $inventory_item->id ); ?>'><?php echo esc_html( $inventory_item->name . ' (' . (int) $inventory_item->available_quantity . ' verfügbar)' ); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><label for='kgvvm-loan-member'><?php echo esc_html__( 'Mitglied', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<select name='user_id' id='kgvvm-loan-member' required>
|
||||||
|
<option value=''><?php echo esc_html__( 'Bitte wählen', KGVVM_TEXT_DOMAIN ); ?></option>
|
||||||
|
<?php foreach ( $members as $member ) : ?>
|
||||||
|
<option value='<?php echo esc_attr( $member->ID ); ?>'><?php echo esc_html( $member->display_name ); ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><label for='kgvvm-loan-quantity'><?php echo esc_html__( 'Menge', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td><input name='quantity' id='kgvvm-loan-quantity' type='number' min='1' required value='1' /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><label for='kgvvm-loan-due'><?php echo esc_html__( 'Rückgabe geplant', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td><input name='due_date' id='kgvvm-loan-due' type='date' value='' /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><label for='kgvvm-loan-note'><?php echo esc_html__( 'Notiz', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td><textarea name='note' id='kgvvm-loan-note' rows='3' class='large-text'></textarea></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<?php submit_button( __( 'Ausleihe speichern', KGVVM_TEXT_DOMAIN ), 'secondary' ); ?>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='kgvvm-card'>
|
||||||
|
<h2><?php echo esc_html__( 'Inventarliste', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||||||
|
<table class='widefat striped'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo esc_html__( 'Name', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Lagerort', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Gesamt', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Verfügbar', 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( $items ) ) : ?>
|
||||||
|
<tr><td colspan='6'><?php echo esc_html__( 'Keine Inventargegenstände gefunden.', KGVVM_TEXT_DOMAIN ); ?></td></tr>
|
||||||
|
<?php else : ?>
|
||||||
|
<?php foreach ( $items as $row ) : ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?php echo esc_html( $row->name ); ?></strong></td>
|
||||||
|
<td><?php echo esc_html( ! empty( $row->storage_location ) ? $row->storage_location : '—' ); ?></td>
|
||||||
|
<td><?php echo esc_html( number_format_i18n( (int) $row->total_quantity, 0 ) ); ?></td>
|
||||||
|
<td><?php echo esc_html( number_format_i18n( (int) $row->available_quantity, 0 ) ); ?></td>
|
||||||
|
<td><?php echo esc_html( (bool) $row->is_active ? __( 'Aktiv', KGVVM_TEXT_DOMAIN ) : __( 'Inaktiv', KGVVM_TEXT_DOMAIN ) ); ?></td>
|
||||||
|
<td>
|
||||||
|
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-inventory', array( '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-inventory', array( 'kgvvm_action' => 'delete_inventory_item', 'id' => $row->id ) ), 'kgvvm_delete_inventory_item_' . $row->id ) ); ?>' onclick='return confirm("<?php echo esc_js( __( 'Inventargegenstand 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>
|
||||||
|
|
||||||
|
<div class='kgvvm-card'>
|
||||||
|
<h2><?php echo esc_html__( 'Offene Ausleihen', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||||||
|
<table class='widefat striped'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo esc_html__( 'Gegenstand', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Mitglied', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Menge', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Ausgeliehen am', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Rückgabe geplant', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Aktion', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if ( empty( $open_loans ) ) : ?>
|
||||||
|
<tr><td colspan='6'><?php echo esc_html__( 'Keine offenen Ausleihen vorhanden.', KGVVM_TEXT_DOMAIN ); ?></td></tr>
|
||||||
|
<?php else : ?>
|
||||||
|
<?php foreach ( $open_loans as $loan ) : ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo esc_html( $loan->item_name ? $loan->item_name : '—' ); ?></td>
|
||||||
|
<td><?php echo esc_html( $loan->borrower_name ? $loan->borrower_name : '—' ); ?></td>
|
||||||
|
<td><?php echo esc_html( number_format_i18n( (int) $loan->borrowed_quantity, 0 ) ); ?></td>
|
||||||
|
<td><?php echo esc_html( ! empty( $loan->borrowed_at ) ? wp_date( 'd.m.Y H:i', strtotime( $loan->borrowed_at ) ) : '—' ); ?></td>
|
||||||
|
<td><?php echo esc_html( ! empty( $loan->due_date ) ? wp_date( 'd.m.Y', strtotime( $loan->due_date ) ) : '—' ); ?></td>
|
||||||
|
<td>
|
||||||
|
<form method='post' style='display:flex; gap:6px; align-items:center;'>
|
||||||
|
<?php wp_nonce_field( 'kgvvm_return_inventory_loan' ); ?>
|
||||||
|
<input type='hidden' name='kgvvm_action' value='return_inventory_loan' />
|
||||||
|
<input type='hidden' name='loan_id' value='<?php echo esc_attr( $loan->id ); ?>' />
|
||||||
|
<input type='text' name='return_note' value='' placeholder='<?php echo esc_attr__( 'Rückgabe-Notiz', KGVVM_TEXT_DOMAIN ); ?>' />
|
||||||
|
<button type='submit' class='button button-small'><?php echo esc_html__( 'Rückgabe', KGVVM_TEXT_DOMAIN ); ?></button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='kgvvm-card'>
|
||||||
|
<h2><?php echo esc_html__( 'Letzte Ausleihvorgänge', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||||||
|
<table class='widefat striped'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th><?php echo esc_html__( 'Zeitpunkt', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Gegenstand', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Mitglied', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Menge', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Status', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php if ( empty( $recent_loans ) ) : ?>
|
||||||
|
<tr><td colspan='5'><?php echo esc_html__( 'Noch keine Ausleihen vorhanden.', KGVVM_TEXT_DOMAIN ); ?></td></tr>
|
||||||
|
<?php else : ?>
|
||||||
|
<?php foreach ( $recent_loans as $row ) : ?>
|
||||||
|
<tr>
|
||||||
|
<td><?php echo esc_html( ! empty( $row->borrowed_at ) ? wp_date( 'd.m.Y H:i', strtotime( $row->borrowed_at ) ) : '—' ); ?></td>
|
||||||
|
<td><?php echo esc_html( $row->item_name ? $row->item_name : '—' ); ?></td>
|
||||||
|
<td><?php echo esc_html( $row->borrower_name ? $row->borrower_name : '—' ); ?></td>
|
||||||
|
<td><?php echo esc_html( number_format_i18n( (int) $row->borrowed_quantity, 0 ) ); ?></td>
|
||||||
|
<td><?php echo esc_html( 'open' === $row->status ? __( 'Offen', KGVVM_TEXT_DOMAIN ) : __( 'Zurückgegeben', KGVVM_TEXT_DOMAIN ) ); ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render dashboard.
|
* Render dashboard.
|
||||||
*
|
*
|
||||||
@@ -989,6 +1488,8 @@ class Admin {
|
|||||||
$date_to = isset( $_GET['date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) ) : '';
|
$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 = isset( $_GET['order'] ) ? strtoupper( sanitize_key( wp_unslash( $_GET['order'] ) ) ) : 'DESC';
|
||||||
$order = 'ASC' === $order ? 'ASC' : 'DESC';
|
$order = 'ASC' === $order ? 'ASC' : 'DESC';
|
||||||
|
$edit_reading_id = isset( $_GET['edit_reading'] ) ? absint( $_GET['edit_reading'] ) : 0;
|
||||||
|
$edit_reading = $edit_reading_id ? $this->readings->find( $edit_reading_id ) : null;
|
||||||
$sections = $this->sections->all_for_options();
|
$sections = $this->sections->all_for_options();
|
||||||
|
|
||||||
if ( $date_from && $date_to && strtotime( $date_from ) > strtotime( $date_to ) ) {
|
if ( $date_from && $date_to && strtotime( $date_from ) > strtotime( $date_to ) ) {
|
||||||
@@ -1051,6 +1552,42 @@ class Admin {
|
|||||||
<h1><?php echo esc_html__( 'Verbrauchsauswertung', KGVVM_TEXT_DOMAIN ); ?></h1>
|
<h1><?php echo esc_html__( 'Verbrauchsauswertung', KGVVM_TEXT_DOMAIN ); ?></h1>
|
||||||
<?php $this->render_notice(); ?>
|
<?php $this->render_notice(); ?>
|
||||||
|
|
||||||
|
<?php if ( $edit_reading ) : ?>
|
||||||
|
<div class='kgvvm-card'>
|
||||||
|
<h2><?php echo esc_html__( 'Ablesung korrigieren', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||||||
|
<form method='post'>
|
||||||
|
<?php wp_nonce_field( 'kgvvm_correct_meter_reading' ); ?>
|
||||||
|
<input type='hidden' name='kgvvm_action' value='correct_meter_reading' />
|
||||||
|
<input type='hidden' name='reading_id' value='<?php echo esc_attr( $edit_reading->id ); ?>' />
|
||||||
|
<input type='hidden' name='section_id' value='<?php echo esc_attr( $section_id ); ?>' />
|
||||||
|
<input type='hidden' name='date_from' value='<?php echo esc_attr( $date_from ); ?>' />
|
||||||
|
<input type='hidden' name='date_to' value='<?php echo esc_attr( $date_to ); ?>' />
|
||||||
|
<input type='hidden' name='order' value='<?php echo esc_attr( $order ); ?>' />
|
||||||
|
<table class='form-table'>
|
||||||
|
<tr>
|
||||||
|
<th><label for='corr_date'><?php echo esc_html__( 'Ablesedatum', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td><input type='date' id='corr_date' name='reading_date' value='<?php echo esc_attr( $edit_reading->reading_date ); ?>' required /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for='corr_value'><?php echo esc_html__( 'Zählerstand', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td><input type='number' id='corr_value' name='reading_value' step='0.001' value='<?php echo esc_attr( number_format( (float) $edit_reading->reading_value, 3, '.', '' ) ); ?>' required /></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for='corr_note'><?php echo esc_html__( 'Korrekturnotiz', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
||||||
|
<td>
|
||||||
|
<textarea id='corr_note' name='note' rows='3' cols='50'><?php echo esc_textarea( $edit_reading->note ); ?></textarea>
|
||||||
|
<p class='description'><?php echo esc_html__( 'Bitte den Grund der Korrektur angeben.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p class='submit'>
|
||||||
|
<button type='submit' class='button button-primary'><?php echo esc_html__( 'Korrektur speichern', KGVVM_TEXT_DOMAIN ); ?></button>
|
||||||
|
<a href='<?php echo esc_url( $this->admin_url( 'kgvvm-consumption', array_filter( array( 'section_id' => $section_id, 'date_from' => $date_from, 'date_to' => $date_to, 'order' => $order ) ) ) ); ?>' class='button'><?php echo esc_html__( 'Abbrechen', KGVVM_TEXT_DOMAIN ); ?></a>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<form method='get' class='kgvvm-toolbar'>
|
<form method='get' class='kgvvm-toolbar'>
|
||||||
<input type='hidden' name='page' value='kgvvm-consumption' />
|
<input type='hidden' name='page' value='kgvvm-consumption' />
|
||||||
<div class='kgvvm-filters'>
|
<div class='kgvvm-filters'>
|
||||||
@@ -1141,23 +1678,49 @@ class Admin {
|
|||||||
<th><?php echo esc_html__( 'Verbrauch seit letzter Ablesung', 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__( 'Preis je Einheit', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
<th><?php echo esc_html__( 'Kosten', KGVVM_TEXT_DOMAIN ); ?></th>
|
<th><?php echo esc_html__( 'Kosten', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<th><?php echo esc_html__( 'Aktionen', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if ( empty( $rows ) ) : ?>
|
<?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>
|
<tr><td colspan='10'><?php echo esc_html__( 'Für die aktuelle Auswahl wurden keine Ablesungen gefunden.', KGVVM_TEXT_DOMAIN ); ?></td></tr>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<?php foreach ( $rows as $row ) : ?>
|
<?php foreach ( $rows as $row ) : ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><?php echo esc_html( wp_date( 'd.m.Y', strtotime( $row->reading_date ) ) ); ?></td>
|
<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->section_name ? $row->section_name : '—' ); ?></td>
|
||||||
<td><?php echo esc_html( $row->parcel_label ? $row->parcel_label : '—' ); ?></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( $this->meter_type_label( $row->type ) . ' – ' . $row->meter_number ); ?>
|
||||||
|
<?php if ( ! empty( $row->note ) ) : ?>
|
||||||
|
<br /><small style='color:#666' title='<?php echo esc_attr( $row->note ); ?>'>📌 <?php echo esc_html( mb_strimwidth( $row->note, 0, 60, '…' ) ); ?></small>
|
||||||
|
<?php endif; ?>
|
||||||
|
</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( 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( $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->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->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>
|
<td><?php echo esc_html( null !== $row->calculated_cost ? $this->format_currency( $row->calculated_cost ) : '—' ); ?></td>
|
||||||
|
<td>
|
||||||
|
<?php
|
||||||
|
$filter_args = array_filter(
|
||||||
|
array(
|
||||||
|
'section_id' => $section_id,
|
||||||
|
'date_from' => $date_from,
|
||||||
|
'date_to' => $date_to,
|
||||||
|
'order' => $order,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$edit_url = $this->admin_url( 'kgvvm-consumption', array_merge( $filter_args, array( 'edit_reading' => $row->id ) ) );
|
||||||
|
$delete_url = wp_nonce_url(
|
||||||
|
$this->admin_url( 'kgvvm-consumption', array_merge( $filter_args, array( 'kgvvm_action' => 'delete_meter_reading', 'id' => $row->id ) ) ),
|
||||||
|
'kgvvm_delete_meter_reading_' . $row->id
|
||||||
|
);
|
||||||
|
?>
|
||||||
|
<a href='<?php echo esc_url( $edit_url ); ?>'><?php echo esc_html__( 'Korrektur', KGVVM_TEXT_DOMAIN ); ?></a>
|
||||||
|
|
|
||||||
|
<a href='<?php echo esc_url( $delete_url ); ?>' onclick='return confirm("<?php echo esc_js( __( 'Ablesung wirklich löschen?', KGVVM_TEXT_DOMAIN ) ); ?>")' style='color:#b32d2e'><?php echo esc_html__( 'Löschen', KGVVM_TEXT_DOMAIN ); ?></a>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -1193,6 +1756,9 @@ class Admin {
|
|||||||
$selected_year = (int) $cost->entry_year;
|
$selected_year = (int) $cost->entry_year;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$year_lock_state = $this->costs->get_statement_lock_state( $selected_year );
|
||||||
|
$year_is_locked = ! empty( $year_lock_state['is_locked'] );
|
||||||
|
|
||||||
$years = $this->costs->get_years( $selected_year );
|
$years = $this->costs->get_years( $selected_year );
|
||||||
$sections = $this->sections->all_for_options( true );
|
$sections = $this->sections->all_for_options( true );
|
||||||
$section_rates = $this->costs->get_section_prices( $selected_year );
|
$section_rates = $this->costs->get_section_prices( $selected_year );
|
||||||
@@ -1255,6 +1821,9 @@ class Admin {
|
|||||||
<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>
|
<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 endif; ?>
|
||||||
<?php $this->render_notice(); ?>
|
<?php $this->render_notice(); ?>
|
||||||
|
<?php if ( $year_is_locked ) : ?>
|
||||||
|
<div class='notice notice-warning'><p><?php echo esc_html__( 'Diese Jahresabrechnung ist festgeschrieben. Änderungen an Kosten, Preisen und Zuordnungen sind gesperrt, bis sie erneut freigegeben wird.', KGVVM_TEXT_DOMAIN ); ?></p></div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<form method='get' class='kgvvm-toolbar' id='kgvvm-cost-filter-form'>
|
<form method='get' class='kgvvm-toolbar' id='kgvvm-cost-filter-form'>
|
||||||
<input type='hidden' name='page' value='kgvvm-costs' />
|
<input type='hidden' name='page' value='kgvvm-costs' />
|
||||||
@@ -1327,7 +1896,7 @@ class Admin {
|
|||||||
<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>
|
<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>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<?php submit_button( $edit_rate ? __( 'Spartenpreise aktualisieren', KGVVM_TEXT_DOMAIN ) : __( 'Spartenpreise speichern', KGVVM_TEXT_DOMAIN ), 'secondary' ); ?>
|
<?php submit_button( $edit_rate ? __( 'Spartenpreise aktualisieren', KGVVM_TEXT_DOMAIN ) : __( 'Spartenpreise speichern', KGVVM_TEXT_DOMAIN ), 'secondary', 'submit', true, $year_is_locked ? array( 'disabled' => 'disabled' ) : array() ); ?>
|
||||||
<?php if ( $edit_rate ) : ?>
|
<?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>
|
<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; ?>
|
<?php endif; ?>
|
||||||
@@ -1373,12 +1942,23 @@ class Admin {
|
|||||||
<p class='kgvvm-help'><?php echo esc_html__( 'Betrag je ausgewählter Einheit, also pro Parzelle oder pro Mitglied.', KGVVM_TEXT_DOMAIN ); ?></p>
|
<p class='kgvvm-help'><?php echo esc_html__( 'Betrag je ausgewählter Einheit, also pro Parzelle oder pro Mitglied.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th scope='row'><?php echo esc_html__( 'Position', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<input type='hidden' name='is_mandatory' value='0' />
|
||||||
|
<input type='checkbox' name='is_mandatory' value='1' <?php checked( $cost ? (bool) $cost->is_mandatory : true, true ); ?> />
|
||||||
|
<?php echo esc_html__( 'Verpflichtende Position', KGVVM_TEXT_DOMAIN ); ?>
|
||||||
|
</label>
|
||||||
|
<p class='kgvvm-help'><?php echo esc_html__( 'Wenn aktiviert: Diese Position wird automatisch in allen Abrechnungen berechnet. Wenn deaktiviert: Diese Position wird als manuelle/optionale Position behandelt.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope='row'><label for='kgvvm-cost-note'><?php echo esc_html__( 'Bemerkung', KGVVM_TEXT_DOMAIN ); ?></label></th>
|
<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>
|
<td><textarea name='note' id='kgvvm-cost-note' rows='4' class='large-text'><?php echo esc_textarea( $cost ? $cost->note : '' ); ?></textarea></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<?php submit_button( $cost ? __( 'Kostenposten aktualisieren', KGVVM_TEXT_DOMAIN ) : __( 'Kostenposten speichern', KGVVM_TEXT_DOMAIN ) ); ?>
|
<?php submit_button( $cost ? __( 'Kostenposten aktualisieren', KGVVM_TEXT_DOMAIN ) : __( 'Kostenposten speichern', KGVVM_TEXT_DOMAIN ), 'primary', 'submit', true, $year_is_locked ? array( 'disabled' => 'disabled' ) : array() ); ?>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1444,7 +2024,13 @@ class Admin {
|
|||||||
<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->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( 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><?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>
|
<td>
|
||||||
|
<?php if ( $year_is_locked ) : ?>
|
||||||
|
<span style='opacity:.65;'><?php echo esc_html__( 'Gesperrt', KGVVM_TEXT_DOMAIN ); ?></span>
|
||||||
|
<?php else : ?>
|
||||||
|
<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>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -1456,6 +2042,7 @@ class Admin {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<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><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__( 'Status', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
<th><?php echo esc_html__( 'Verteilung', KGVVM_TEXT_DOMAIN ); ?></th>
|
<th><?php echo esc_html__( 'Verteilung', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
<th><?php echo esc_html__( 'Betrag', 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><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>
|
||||||
@@ -1466,20 +2053,31 @@ class Admin {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php if ( empty( $rows ) ) : ?>
|
<?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>
|
<tr><td colspan='8'><?php echo esc_html__( 'Für das gewählte Jahr sind noch keine Kostenposten vorhanden.', KGVVM_TEXT_DOMAIN ); ?></td></tr>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<?php foreach ( $rows as $row ) : ?>
|
<?php foreach ( $rows as $row ) : ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong><?php echo esc_html( $row->name ); ?></strong></td>
|
<td><strong><?php echo esc_html( $row->name ); ?></strong></td>
|
||||||
|
<td>
|
||||||
|
<?php if ( ! isset( $row->is_mandatory ) || (bool) $row->is_mandatory ) : ?>
|
||||||
|
<span style='color:#007017;'><?php echo esc_html__( 'Verpflichtend', KGVVM_TEXT_DOMAIN ); ?></span>
|
||||||
|
<?php else : ?>
|
||||||
|
<span style='color:#9a6700;'><?php echo esc_html__( 'Manuell', KGVVM_TEXT_DOMAIN ); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</td>
|
||||||
<td><?php echo esc_html( $this->get_cost_distribution_label( $row->distribution_type ) ); ?></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->unit_amount ) ); ?></td>
|
||||||
<td><?php echo esc_html( $this->format_currency( $row->calculated_total_cost ) ); ?></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( $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><?php echo esc_html( ! empty( $row->updated_at ) ? wp_date( 'd.m.Y H:i', strtotime( $row->updated_at ) ) : '—' ); ?></td>
|
||||||
<td>
|
<td>
|
||||||
|
<?php if ( $year_is_locked ) : ?>
|
||||||
|
<span style='opacity:.65;'><?php echo esc_html__( 'Gesperrt', KGVVM_TEXT_DOMAIN ); ?></span>
|
||||||
|
<?php else : ?>
|
||||||
<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( $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>
|
<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>
|
||||||
|
<?php endif; ?>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
@@ -1586,6 +2184,8 @@ class Admin {
|
|||||||
$statement_type = isset( $_GET['statement_type'] ) ? sanitize_key( wp_unslash( $_GET['statement_type'] ) ) : 'parcel';
|
$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';
|
$output = isset( $_GET['output'] ) ? sanitize_key( wp_unslash( $_GET['output'] ) ) : 'html';
|
||||||
$subject_id = absint( isset( $_GET['subject_id'] ) ? $_GET['subject_id'] : 0 );
|
$subject_id = absint( isset( $_GET['subject_id'] ) ? $_GET['subject_id'] : 0 );
|
||||||
|
$lock_state = $this->costs->get_statement_lock_state( $year );
|
||||||
|
$statement_is_locked = ! empty( $lock_state['is_locked'] );
|
||||||
$date_from = sprintf( '%04d-01-01', $year );
|
$date_from = sprintf( '%04d-01-01', $year );
|
||||||
$date_to = sprintf( '%04d-12-31', $year );
|
$date_to = sprintf( '%04d-12-31', $year );
|
||||||
$all_rows = $this->readings->get_consumption_report( 0, $date_from, $date_to, 'ASC' );
|
$all_rows = $this->readings->get_consumption_report( 0, $date_from, $date_to, 'ASC' );
|
||||||
@@ -1705,22 +2305,28 @@ class Admin {
|
|||||||
$fixed_items = array();
|
$fixed_items = array();
|
||||||
$fixed_total = 0.0;
|
$fixed_total = 0.0;
|
||||||
|
|
||||||
// Load parcel-specific cost assignments so that entries with explicit
|
// Load parcel-specific cost assignments so entries can either apply to
|
||||||
// assignments are only charged to the assigned parcels.
|
// all parcels (mandatory) or only to explicitly assigned parcels (manual).
|
||||||
$entries_with_assignments = ( 'parcel' === $statement_type )
|
$entries_with_assignments = $this->costs->get_entry_ids_with_assignments( $year );
|
||||||
? $this->costs->get_entry_ids_with_assignments( $year )
|
$subject_assigned_ids = array();
|
||||||
: array();
|
|
||||||
$parcel_assigned_ids = ( 'parcel' === $statement_type )
|
foreach ( array_map( 'intval', $parcel_ids ) as $parcel_id ) {
|
||||||
? $this->costs->get_assigned_entry_ids( $subject_id )
|
$subject_assigned_ids = array_merge( $subject_assigned_ids, $this->costs->get_assigned_entry_ids( $parcel_id ) );
|
||||||
: array();
|
}
|
||||||
|
|
||||||
|
$subject_assigned_ids = array_values( array_unique( array_map( 'intval', $subject_assigned_ids ) ) );
|
||||||
|
|
||||||
foreach ( $cost_entries as $entry ) {
|
foreach ( $cost_entries as $entry ) {
|
||||||
$entry_id = (int) $entry->id;
|
$entry_id = (int) $entry->id;
|
||||||
$has_assignments = in_array( $entry_id, $entries_with_assignments, true );
|
$has_assignments = in_array( $entry_id, $entries_with_assignments, true );
|
||||||
|
$is_assigned = in_array( $entry_id, $subject_assigned_ids, true );
|
||||||
|
$is_mandatory = ! isset( $entry->is_mandatory ) || (bool) $entry->is_mandatory;
|
||||||
|
|
||||||
// For parcel statements: if this entry has any parcel restrictions,
|
if ( ! $is_mandatory && ! $is_assigned ) {
|
||||||
// skip it unless this specific parcel is assigned.
|
continue;
|
||||||
if ( 'parcel' === $statement_type && $has_assignments && ! in_array( $entry_id, $parcel_assigned_ids, true ) ) {
|
}
|
||||||
|
|
||||||
|
if ( $is_mandatory && $has_assignments && ! $is_assigned ) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1778,9 +2384,24 @@ class Admin {
|
|||||||
</style>
|
</style>
|
||||||
<div class='wrap kgvvm-print-page'>
|
<div class='wrap kgvvm-print-page'>
|
||||||
<h1><?php echo esc_html( $subject_label ); ?></h1>
|
<h1><?php echo esc_html( $subject_label ); ?></h1>
|
||||||
|
<?php if ( $statement_is_locked ) : ?>
|
||||||
|
<div class='notice notice-warning'><p><?php echo esc_html__( 'Diese Jahresabrechnung ist festgeschrieben. Änderungen sind erst nach erneuter Freigabe möglich.', KGVVM_TEXT_DOMAIN ); ?></p></div>
|
||||||
|
<?php endif; ?>
|
||||||
<div class='kgvvm-print-actions'>
|
<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='<?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='#' class='button button-secondary' onclick='window.print(); return false;'><?php echo esc_html__( 'Drucken / Als PDF speichern', KGVVM_TEXT_DOMAIN ); ?></a>
|
||||||
|
<form method='post' action='<?php echo esc_url( admin_url( 'admin.php' ) ); ?>' style='display:inline-block;'>
|
||||||
|
<?php wp_nonce_field( 'kgvvm_set_statement_lock_' . $year ); ?>
|
||||||
|
<input type='hidden' name='kgvvm_action' value='set_statement_lock' />
|
||||||
|
<input type='hidden' name='year' value='<?php echo esc_attr( $year ); ?>' />
|
||||||
|
<input type='hidden' name='statement_type' value='<?php echo esc_attr( $statement_type ); ?>' />
|
||||||
|
<input type='hidden' name='subject_id' value='<?php echo esc_attr( $subject_id ); ?>' />
|
||||||
|
<input type='hidden' name='lock_mode' value='<?php echo esc_attr( $statement_is_locked ? 'unlock' : 'lock' ); ?>' />
|
||||||
|
<input type='hidden' name='page' value='kgvvm-costs' />
|
||||||
|
<button type='submit' class='button <?php echo esc_attr( $statement_is_locked ? 'button-secondary' : 'button-primary' ); ?>'>
|
||||||
|
<?php echo esc_html( $statement_is_locked ? __( 'Erneut freigeben', KGVVM_TEXT_DOMAIN ) : __( 'Jahresabrechnung festschreiben', KGVVM_TEXT_DOMAIN ) ); ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
<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>
|
<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>
|
</div>
|
||||||
|
|
||||||
@@ -1848,8 +2469,7 @@ class Admin {
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><?php echo esc_html__( 'Kostenposten', KGVVM_TEXT_DOMAIN ); ?></th>
|
<th><?php echo esc_html__( 'Kostenposten', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
<th><?php echo esc_html__( 'Gesamtkosten', KGVVM_TEXT_DOMAIN ); ?></th>
|
<th style='width:140px; white-space:nowrap;'><?php echo esc_html__( 'Anteil', KGVVM_TEXT_DOMAIN ); ?></th>
|
||||||
<th><?php echo esc_html__( 'Anteil', KGVVM_TEXT_DOMAIN ); ?></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -1860,8 +2480,7 @@ class Admin {
|
|||||||
<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>
|
<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>' : ''; ?>
|
<?php echo $item['note'] ? '<br /><span class="kgvvm-help">' . esc_html( $item['note'] ) . '</span>' : ''; ?>
|
||||||
</td>
|
</td>
|
||||||
<td><?php echo esc_html( $this->format_currency( $item['total'] ) ); ?></td>
|
<td style='width:140px; white-space:nowrap; text-align:right;'><?php echo esc_html( $this->format_currency( $item['share'] ) ); ?></td>
|
||||||
<td><?php echo esc_html( $this->format_currency( $item['share'] ) ); ?></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -1889,6 +2508,29 @@ class Admin {
|
|||||||
|
|
||||||
<?php if ( 'parcel' === $statement_type ) : ?>
|
<?php if ( 'parcel' === $statement_type ) : ?>
|
||||||
<div class='kgvvm-statement-sidebar'>
|
<div class='kgvvm-statement-sidebar'>
|
||||||
|
<div class='kgvvm-card'>
|
||||||
|
<h3 style='margin-top:0;'><?php echo esc_html__( 'Freigabe-Status', KGVVM_TEXT_DOMAIN ); ?></h3>
|
||||||
|
<p>
|
||||||
|
<?php if ( $statement_is_locked ) : ?>
|
||||||
|
<strong style='color:#b32d2e;'><?php echo esc_html__( 'Festgeschrieben', KGVVM_TEXT_DOMAIN ); ?></strong>
|
||||||
|
<?php else : ?>
|
||||||
|
<strong style='color:#007017;'><?php echo esc_html__( 'Freigegeben', KGVVM_TEXT_DOMAIN ); ?></strong>
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
|
<form method='post' action='<?php echo esc_url( admin_url( 'admin.php' ) ); ?>'>
|
||||||
|
<?php wp_nonce_field( 'kgvvm_set_statement_lock_' . $year ); ?>
|
||||||
|
<input type='hidden' name='kgvvm_action' value='set_statement_lock' />
|
||||||
|
<input type='hidden' name='year' value='<?php echo esc_attr( $year ); ?>' />
|
||||||
|
<input type='hidden' name='statement_type' value='<?php echo esc_attr( $statement_type ); ?>' />
|
||||||
|
<input type='hidden' name='subject_id' value='<?php echo esc_attr( $subject_id ); ?>' />
|
||||||
|
<input type='hidden' name='lock_mode' value='<?php echo esc_attr( $statement_is_locked ? 'unlock' : 'lock' ); ?>' />
|
||||||
|
<input type='hidden' name='page' value='kgvvm-costs' />
|
||||||
|
<button type='submit' class='button <?php echo esc_attr( $statement_is_locked ? 'button-secondary' : 'button-primary' ); ?>'>
|
||||||
|
<?php echo esc_html( $statement_is_locked ? __( 'Erneut freigeben', KGVVM_TEXT_DOMAIN ) : __( 'Jahresabrechnung festschreiben', KGVVM_TEXT_DOMAIN ) ); ?>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class='kgvvm-card'>
|
<div class='kgvvm-card'>
|
||||||
<h3 style='margin-top:0;'><?php echo esc_html__( 'Kostenpositionen', KGVVM_TEXT_DOMAIN ); ?></h3>
|
<h3 style='margin-top:0;'><?php echo esc_html__( 'Kostenpositionen', KGVVM_TEXT_DOMAIN ); ?></h3>
|
||||||
<p class='description'><?php echo esc_html__( 'Hier legen Sie fest, welche Positionen dieser Parzelle berechnet werden. Positionen ohne Einschränkung gelten für alle Parzellen.', KGVVM_TEXT_DOMAIN ); ?></p>
|
<p class='description'><?php echo esc_html__( 'Hier legen Sie fest, welche Positionen dieser Parzelle berechnet werden. Positionen ohne Einschränkung gelten für alle Parzellen.', KGVVM_TEXT_DOMAIN ); ?></p>
|
||||||
@@ -1899,21 +2541,24 @@ class Admin {
|
|||||||
<ul class='kgvvm-cost-assignment-list'>
|
<ul class='kgvvm-cost-assignment-list'>
|
||||||
<?php foreach ( $cost_entries as $entry ) :
|
<?php foreach ( $cost_entries as $entry ) :
|
||||||
$entry_id = (int) $entry->id;
|
$entry_id = (int) $entry->id;
|
||||||
$is_assigned = in_array( $entry_id, $parcel_assigned_ids, true );
|
$is_assigned = in_array( $entry_id, $subject_assigned_ids, true );
|
||||||
$has_any = in_array( $entry_id, $entries_with_assignments, true );
|
$has_any = in_array( $entry_id, $entries_with_assignments, true );
|
||||||
|
$is_mandatory = ! isset( $entry->is_mandatory ) || (bool) $entry->is_mandatory;
|
||||||
?>
|
?>
|
||||||
<li>
|
<li>
|
||||||
<span class='kgvvm-entry-name'>
|
<span class='kgvvm-entry-name'>
|
||||||
<?php if ( $is_assigned ) : ?>
|
<?php if ( $is_assigned ) : ?>
|
||||||
<span style='color:#007017;' title='<?php esc_attr_e( 'Diese Parzelle ist zugeordnet', KGVVM_TEXT_DOMAIN ); ?>'>✓</span>
|
<span style='color:#007017;' title='<?php esc_attr_e( 'Diese Parzelle ist zugeordnet', KGVVM_TEXT_DOMAIN ); ?>'>✓</span>
|
||||||
<?php elseif ( $has_any ) : ?>
|
<?php elseif ( ! $is_mandatory || $has_any ) : ?>
|
||||||
<span style='color:#b32d2e;' title='<?php esc_attr_e( 'Diese Parzelle ist nicht zugeordnet', KGVVM_TEXT_DOMAIN ); ?>'>✗</span>
|
<span style='color:#b32d2e;' title='<?php esc_attr_e( 'Diese Parzelle ist nicht zugeordnet', KGVVM_TEXT_DOMAIN ); ?>'>✗</span>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<span style='color:#999;' title='<?php esc_attr_e( 'Gilt für alle Parzellen', KGVVM_TEXT_DOMAIN ); ?>'>—</span>
|
<span style='color:#999;' title='<?php esc_attr_e( 'Gilt für alle Parzellen', KGVVM_TEXT_DOMAIN ); ?>'>—</span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php echo esc_html( $entry->name ); ?>
|
<?php echo esc_html( $entry->name ); ?>
|
||||||
</span>
|
</span>
|
||||||
<?php if ( $is_assigned ) : ?>
|
<?php if ( $statement_is_locked ) : ?>
|
||||||
|
<span class='button button-small' style='pointer-events:none; opacity:.65;'><?php echo esc_html__( 'Gesperrt', KGVVM_TEXT_DOMAIN ); ?></span>
|
||||||
|
<?php elseif ( $is_assigned ) : ?>
|
||||||
<form method='post' action='<?php echo esc_url( admin_url( 'admin.php' ) ); ?>' style='display:inline;'>
|
<form method='post' action='<?php echo esc_url( admin_url( 'admin.php' ) ); ?>' style='display:inline;'>
|
||||||
<?php wp_nonce_field( 'kgvvm_toggle_parcel_cost' ); ?>
|
<?php wp_nonce_field( 'kgvvm_toggle_parcel_cost' ); ?>
|
||||||
<input type='hidden' name='kgvvm_action' value='toggle_parcel_cost_assignment' />
|
<input type='hidden' name='kgvvm_action' value='toggle_parcel_cost_assignment' />
|
||||||
@@ -1924,6 +2569,8 @@ class Admin {
|
|||||||
<input type='hidden' name='page' value='kgvvm-costs' />
|
<input type='hidden' name='page' value='kgvvm-costs' />
|
||||||
<button type='submit' class='button button-small'><?php echo esc_html__( 'Entfernen', KGVVM_TEXT_DOMAIN ); ?></button>
|
<button type='submit' class='button button-small'><?php echo esc_html__( 'Entfernen', KGVVM_TEXT_DOMAIN ); ?></button>
|
||||||
</form>
|
</form>
|
||||||
|
<?php elseif ( $is_mandatory && ! $has_any ) : ?>
|
||||||
|
<span class='button button-small' style='pointer-events:none; opacity:.65;'><?php echo esc_html__( 'Automatisch', KGVVM_TEXT_DOMAIN ); ?></span>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<form method='post' action='<?php echo esc_url( admin_url( 'admin.php' ) ); ?>' style='display:inline;'>
|
<form method='post' action='<?php echo esc_url( admin_url( 'admin.php' ) ); ?>' style='display:inline;'>
|
||||||
<?php wp_nonce_field( 'kgvvm_toggle_parcel_cost' ); ?>
|
<?php wp_nonce_field( 'kgvvm_toggle_parcel_cost' ); ?>
|
||||||
@@ -1941,8 +2588,8 @@ class Admin {
|
|||||||
</ul>
|
</ul>
|
||||||
<p class='description' style='margin-top:12px; font-size:11px;'>
|
<p class='description' style='margin-top:12px; font-size:11px;'>
|
||||||
<span style='color:#007017;'>✓</span> <?php echo esc_html__( 'zugeordnet', KGVVM_TEXT_DOMAIN ); ?>
|
<span style='color:#007017;'>✓</span> <?php echo esc_html__( 'zugeordnet', KGVVM_TEXT_DOMAIN ); ?>
|
||||||
<span style='color:#b32d2e;'>✗</span> <?php echo esc_html__( 'nicht zugeordnet', KGVVM_TEXT_DOMAIN ); ?>
|
<span style='color:#b32d2e;'>✗</span> <?php echo esc_html__( 'manuell oder ausgeschlossen', KGVVM_TEXT_DOMAIN ); ?>
|
||||||
<span style='color:#999;'>—</span> <?php echo esc_html__( 'alle Parzellen', KGVVM_TEXT_DOMAIN ); ?>
|
<span style='color:#999;'>—</span> <?php echo esc_html__( 'Pflichtposition für alle Parzellen', KGVVM_TEXT_DOMAIN ); ?>
|
||||||
</p>
|
</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
@@ -2044,24 +2691,24 @@ class Admin {
|
|||||||
<h2><?php echo esc_html__( 'Zusammenfassung', KGVVM_TEXT_DOMAIN ); ?></h2>
|
<h2><?php echo esc_html__( 'Zusammenfassung', KGVVM_TEXT_DOMAIN ); ?></h2>
|
||||||
<table cellpadding="5" cellspacing="0" border="1">
|
<table cellpadding="5" cellspacing="0" border="1">
|
||||||
<tr>
|
<tr>
|
||||||
<th><strong><?php echo esc_html__( 'Position', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
<th style="width:76%;"><strong><?php echo esc_html__( 'Position', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
||||||
<th><strong><?php echo esc_html__( 'Betrag', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
<th style="width:24%; text-align:right; white-space:nowrap;"><strong><?php echo esc_html__( 'Betrag', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><?php echo esc_html__( 'Anteilige Grundkosten', KGVVM_TEXT_DOMAIN ); ?></td>
|
<td><?php echo esc_html__( 'Anteilige Grundkosten', KGVVM_TEXT_DOMAIN ); ?></td>
|
||||||
<td><?php echo esc_html( $this->format_currency( $statement['fixed_total'] ) ); ?></td>
|
<td style="text-align:right; white-space:nowrap;"><?php echo esc_html( $this->format_currency( $statement['fixed_total'] ) ); ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><?php echo esc_html__( 'Wasserkosten', KGVVM_TEXT_DOMAIN ); ?></td>
|
<td><?php echo esc_html__( 'Wasserkosten', KGVVM_TEXT_DOMAIN ); ?></td>
|
||||||
<td><?php echo esc_html( $this->format_currency( $statement['water_cost_total'] ) ); ?></td>
|
<td style="text-align:right; white-space:nowrap;"><?php echo esc_html( $this->format_currency( $statement['water_cost_total'] ) ); ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><?php echo esc_html__( 'Stromkosten', KGVVM_TEXT_DOMAIN ); ?></td>
|
<td><?php echo esc_html__( 'Stromkosten', KGVVM_TEXT_DOMAIN ); ?></td>
|
||||||
<td><?php echo esc_html( $this->format_currency( $statement['power_cost_total'] ) ); ?></td>
|
<td style="text-align:right; white-space:nowrap;"><?php echo esc_html( $this->format_currency( $statement['power_cost_total'] ) ); ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong><?php echo esc_html__( 'Gesamtbetrag', KGVVM_TEXT_DOMAIN ); ?></strong></td>
|
<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>
|
<td style="text-align:right; white-space:nowrap;"><strong><?php echo esc_html( $this->format_currency( $statement['grand_total'] ) ); ?></strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@@ -2071,15 +2718,13 @@ class Admin {
|
|||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<table cellpadding="5" cellspacing="0" border="1">
|
<table cellpadding="5" cellspacing="0" border="1">
|
||||||
<tr>
|
<tr>
|
||||||
<th><strong><?php echo esc_html__( 'Kostenposten', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
<th style="width:76%;"><strong><?php echo esc_html__( 'Kostenposten', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
||||||
<th><strong><?php echo esc_html__( 'Gesamtkosten', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
<th style="width:24%; text-align:right; white-space:nowrap;"><strong><?php echo esc_html__( 'Anteil', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
||||||
<th><strong><?php echo esc_html__( 'Anteil', KGVVM_TEXT_DOMAIN ); ?></strong></th>
|
|
||||||
</tr>
|
</tr>
|
||||||
<?php foreach ( $statement['fixed_items'] as $item ) : ?>
|
<?php foreach ( $statement['fixed_items'] as $item ) : ?>
|
||||||
<tr>
|
<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 style="width:76%;"><?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 style="width:24%; text-align:right; white-space:nowrap;"><?php echo esc_html( $this->format_currency( $item['share'] ) ); ?></td>
|
||||||
<td><?php echo esc_html( $this->format_currency( $item['share'] ) ); ?></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
0
includes/Autoloader.php
Normal file → Executable file
0
includes/Autoloader.php
Normal file → Executable file
2
includes/DataTransfer.php
Normal file → Executable file
2
includes/DataTransfer.php
Normal file → Executable file
@@ -25,6 +25,7 @@ class DataTransfer {
|
|||||||
'sections',
|
'sections',
|
||||||
'parcels',
|
'parcels',
|
||||||
'tenants',
|
'tenants',
|
||||||
|
'inventory_items',
|
||||||
'parcel_members',
|
'parcel_members',
|
||||||
'parcel_tenants',
|
'parcel_tenants',
|
||||||
'meters',
|
'meters',
|
||||||
@@ -38,6 +39,7 @@ class DataTransfer {
|
|||||||
'work_year_config',
|
'work_year_config',
|
||||||
'work_logs',
|
'work_logs',
|
||||||
'work_log_members',
|
'work_log_members',
|
||||||
|
'inventory_loans',
|
||||||
);
|
);
|
||||||
|
|
||||||
public function __construct( \wpdb $wpdb ) {
|
public function __construct( \wpdb $wpdb ) {
|
||||||
|
|||||||
0
includes/Deactivator.php
Normal file → Executable file
0
includes/Deactivator.php
Normal file → Executable file
0
includes/Plugin.php
Normal file → Executable file
0
includes/Plugin.php
Normal file → Executable file
0
includes/Repositories/AbstractRepository.php
Normal file → Executable file
0
includes/Repositories/AbstractRepository.php
Normal file → Executable file
0
includes/Repositories/AssignmentRepository.php
Normal file → Executable file
0
includes/Repositories/AssignmentRepository.php
Normal file → Executable file
74
includes/Repositories/CostRepository.php
Normal file → Executable file
74
includes/Repositories/CostRepository.php
Normal file → Executable file
@@ -93,11 +93,12 @@ class CostRepository extends AbstractRepository {
|
|||||||
'distribution_type' => isset( $data['distribution_type'] ) ? $data['distribution_type'] : 'parcel',
|
'distribution_type' => isset( $data['distribution_type'] ) ? $data['distribution_type'] : 'parcel',
|
||||||
'unit_amount' => isset( $data['unit_amount'] ) ? (float) $data['unit_amount'] : 0,
|
'unit_amount' => isset( $data['unit_amount'] ) ? (float) $data['unit_amount'] : 0,
|
||||||
'total_cost' => (float) $data['total_cost'],
|
'total_cost' => (float) $data['total_cost'],
|
||||||
|
'is_mandatory' => isset( $data['is_mandatory'] ) ? (int) (bool) $data['is_mandatory'] : 1,
|
||||||
'note' => $data['note'],
|
'note' => $data['note'],
|
||||||
'updated_at' => $this->now(),
|
'updated_at' => $this->now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
$formats = array( '%d', '%s', '%s', '%f', '%f', '%s', '%s' );
|
$formats = array( '%d', '%s', '%s', '%f', '%f', '%d', '%s', '%s' );
|
||||||
|
|
||||||
$this->ensure_year( $payload['entry_year'] );
|
$this->ensure_year( $payload['entry_year'] );
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ class CostRepository extends AbstractRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$payload['created_at'] = $this->now();
|
$payload['created_at'] = $this->now();
|
||||||
$this->wpdb->insert( $this->table, $payload, array( '%d', '%s', '%s', '%f', '%f', '%s', '%s', '%s' ) );
|
$this->wpdb->insert( $this->table, $payload, array( '%d', '%s', '%s', '%f', '%f', '%d', '%s', '%s', '%s' ) );
|
||||||
|
|
||||||
return $this->wpdb->insert_id;
|
return $this->wpdb->insert_id;
|
||||||
}
|
}
|
||||||
@@ -221,6 +222,75 @@ class CostRepository extends AbstractRepository {
|
|||||||
return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$year_table} WHERE entry_year = %d", $year ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
return $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$year_table} WHERE entry_year = %d", $year ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get lock state for one statement year.
|
||||||
|
*
|
||||||
|
* @param int $year Selected year.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_statement_lock_state( $year ) {
|
||||||
|
$details = $this->get_year_details( $year );
|
||||||
|
|
||||||
|
if ( ! $details ) {
|
||||||
|
return array(
|
||||||
|
'is_locked' => false,
|
||||||
|
'locked_at' => null,
|
||||||
|
'locked_by' => 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'is_locked' => ! empty( $details->statement_is_locked ) && 1 === (int) $details->statement_is_locked,
|
||||||
|
'locked_at' => isset( $details->statement_locked_at ) ? $details->statement_locked_at : null,
|
||||||
|
'locked_by' => isset( $details->statement_locked_by ) ? (int) $details->statement_locked_by : 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether statement data for one year is locked.
|
||||||
|
*
|
||||||
|
* @param int $year Selected year.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function is_statement_locked( $year ) {
|
||||||
|
$state = $this->get_statement_lock_state( $year );
|
||||||
|
|
||||||
|
return ! empty( $state['is_locked'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock or unlock a statement year.
|
||||||
|
*
|
||||||
|
* @param int $year Selected year.
|
||||||
|
* @param bool $is_locked Target lock state.
|
||||||
|
* @param int $locked_by User ID that changes the state.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function set_statement_lock( $year, $is_locked, $locked_by = 0 ) {
|
||||||
|
$year = absint( $year );
|
||||||
|
|
||||||
|
if ( $year < 1 || ! $this->ensure_year( $year ) ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = array(
|
||||||
|
'statement_is_locked' => $is_locked ? 1 : 0,
|
||||||
|
'statement_locked_at' => $is_locked ? $this->now() : null,
|
||||||
|
'statement_locked_by' => $is_locked ? absint( $locked_by ) : 0,
|
||||||
|
'updated_at' => $this->now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
$result = $this->wpdb->update(
|
||||||
|
$this->year_table(),
|
||||||
|
$payload,
|
||||||
|
array( 'entry_year' => $year ),
|
||||||
|
array( '%d', '%s', '%d', '%s' ),
|
||||||
|
array( '%d' )
|
||||||
|
);
|
||||||
|
|
||||||
|
return false !== $result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save section-specific yearly prices.
|
* Save section-specific yearly prices.
|
||||||
*
|
*
|
||||||
|
|||||||
336
includes/Repositories/InventoryRepository.php
Normal file
336
includes/Repositories/InventoryRepository.php
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Inventory repository.
|
||||||
|
*
|
||||||
|
* @package KGV\VereinManager
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace KGV\VereinManager\Repositories;
|
||||||
|
|
||||||
|
use KGV\VereinManager\Schema;
|
||||||
|
|
||||||
|
if ( ! defined( 'ABSPATH' ) ) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
class InventoryRepository extends AbstractRepository {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve main inventory table name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function resolve_table() {
|
||||||
|
return Schema::table( 'inventory_items' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve loan table name.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function loans_table() {
|
||||||
|
return Schema::table( 'inventory_loans' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search inventory items.
|
||||||
|
*
|
||||||
|
* @param array $args Query arguments.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function search_items( $args = array() ) {
|
||||||
|
$search = isset( $args['s'] ) ? sanitize_text_field( wp_unslash( $args['s'] ) ) : '';
|
||||||
|
$status = isset( $args['status'] ) ? sanitize_key( wp_unslash( $args['status'] ) ) : '';
|
||||||
|
$orderby = $this->sanitize_orderby( isset( $args['orderby'] ) ? sanitize_key( wp_unslash( $args['orderby'] ) ) : 'name', array( 'name', 'total_quantity', 'available_quantity', 'is_active', 'updated_at' ), 'name' );
|
||||||
|
$order = $this->sanitize_order( isset( $args['order'] ) ? sanitize_key( wp_unslash( $args['order'] ) ) : 'ASC' );
|
||||||
|
|
||||||
|
$sql = "SELECT * FROM {$this->table} WHERE 1=1";
|
||||||
|
$params = array();
|
||||||
|
|
||||||
|
if ( '' !== $search ) {
|
||||||
|
$like = '%' . $this->wpdb->esc_like( $search ) . '%';
|
||||||
|
$sql .= ' AND (name LIKE %s OR storage_location LIKE %s OR description LIKE %s)';
|
||||||
|
$params[] = $like;
|
||||||
|
$params[] = $like;
|
||||||
|
$params[] = $like;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( in_array( $status, array( 'active', 'inactive' ), true ) ) {
|
||||||
|
$sql .= ' AND is_active = %d';
|
||||||
|
$params[] = 'active' === $status ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY {$orderby} {$order}, id DESC";
|
||||||
|
|
||||||
|
if ( ! empty( $params ) ) {
|
||||||
|
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $params ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save or update one inventory item.
|
||||||
|
*
|
||||||
|
* @param array $data Item payload.
|
||||||
|
* @param int $id Optional item ID.
|
||||||
|
* @return int|false
|
||||||
|
*/
|
||||||
|
public function save_item( $data, $id = 0 ) {
|
||||||
|
$payload = array(
|
||||||
|
'name' => $data['name'],
|
||||||
|
'total_quantity' => max( 0, (int) $data['total_quantity'] ),
|
||||||
|
'available_quantity' => max( 0, (int) $data['available_quantity'] ),
|
||||||
|
'storage_location' => isset( $data['storage_location'] ) ? $data['storage_location'] : '',
|
||||||
|
'description' => isset( $data['description'] ) ? $data['description'] : '',
|
||||||
|
'is_active' => ! empty( $data['is_active'] ) ? 1 : 0,
|
||||||
|
'updated_at' => $this->now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( $payload['available_quantity'] > $payload['total_quantity'] ) {
|
||||||
|
$payload['available_quantity'] = $payload['total_quantity'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $id > 0 ) {
|
||||||
|
$result = $this->wpdb->update(
|
||||||
|
$this->table,
|
||||||
|
$payload,
|
||||||
|
array( 'id' => absint( $id ) ),
|
||||||
|
array( '%s', '%d', '%d', '%s', '%s', '%d', '%s' ),
|
||||||
|
array( '%d' )
|
||||||
|
);
|
||||||
|
|
||||||
|
return false !== $result ? (int) $id : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload['created_at'] = $this->now();
|
||||||
|
$result = $this->wpdb->insert( $this->table, $payload, array( '%s', '%d', '%d', '%s', '%s', '%d', '%s', '%s' ) );
|
||||||
|
|
||||||
|
return false !== $result ? (int) $this->wpdb->insert_id : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether one item has open loans.
|
||||||
|
*
|
||||||
|
* @param int $item_id Item ID.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function has_open_loans( $item_id ) {
|
||||||
|
$item_id = absint( $item_id );
|
||||||
|
$table = $this->loans_table();
|
||||||
|
|
||||||
|
if ( $item_id < 1 || '' === $table ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$count = (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COUNT(*) FROM {$table} WHERE item_id = %d AND status = %s", $item_id, 'open' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
|
||||||
|
return $count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get total currently borrowed quantity for one item.
|
||||||
|
*
|
||||||
|
* @param int $item_id Item ID.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function get_open_borrowed_quantity( $item_id ) {
|
||||||
|
$item_id = absint( $item_id );
|
||||||
|
$table = $this->loans_table();
|
||||||
|
|
||||||
|
if ( $item_id < 1 || '' === $table ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sum = (int) $this->wpdb->get_var( $this->wpdb->prepare( "SELECT COALESCE(SUM(borrowed_quantity), 0) FROM {$table} WHERE item_id = %d AND status = %s", $item_id, 'open' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
|
||||||
|
return max( 0, $sum );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Borrow an inventory item.
|
||||||
|
*
|
||||||
|
* @param int $item_id Item ID.
|
||||||
|
* @param int $user_id Borrower user ID.
|
||||||
|
* @param int $quantity Quantity.
|
||||||
|
* @param string $due_date Optional due date.
|
||||||
|
* @param string $note Optional note.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function borrow_item( $item_id, $user_id, $quantity, $due_date = '', $note = '' ) {
|
||||||
|
$item_id = absint( $item_id );
|
||||||
|
$user_id = absint( $user_id );
|
||||||
|
$quantity = max( 0, (int) $quantity );
|
||||||
|
$loan_tab = $this->loans_table();
|
||||||
|
|
||||||
|
if ( $item_id < 1 || $user_id < 1 || $quantity < 1 || '' === $loan_tab ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = $this->find( $item_id );
|
||||||
|
|
||||||
|
if ( ! $item || (int) $item->available_quantity < $quantity ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->wpdb->query( 'START TRANSACTION' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
|
||||||
|
$inserted = $this->wpdb->insert(
|
||||||
|
$loan_tab,
|
||||||
|
array(
|
||||||
|
'item_id' => $item_id,
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'borrowed_quantity' => $quantity,
|
||||||
|
'borrowed_at' => $this->now(),
|
||||||
|
'due_date' => '' !== $due_date ? $due_date : null,
|
||||||
|
'returned_at' => null,
|
||||||
|
'note' => $note,
|
||||||
|
'return_note' => '',
|
||||||
|
'status' => 'open',
|
||||||
|
'created_at' => $this->now(),
|
||||||
|
'updated_at' => $this->now(),
|
||||||
|
),
|
||||||
|
array( '%d', '%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( false === $inserted ) {
|
||||||
|
$this->wpdb->query( 'ROLLBACK' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$updated = $this->wpdb->update(
|
||||||
|
$this->table,
|
||||||
|
array(
|
||||||
|
'available_quantity' => max( 0, (int) $item->available_quantity - $quantity ),
|
||||||
|
'updated_at' => $this->now(),
|
||||||
|
),
|
||||||
|
array( 'id' => $item_id ),
|
||||||
|
array( '%d', '%s' ),
|
||||||
|
array( '%d' )
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( false === $updated ) {
|
||||||
|
$this->wpdb->query( 'ROLLBACK' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->wpdb->query( 'COMMIT' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return one open loan.
|
||||||
|
*
|
||||||
|
* @param int $loan_id Loan ID.
|
||||||
|
* @param string $return_note Optional return note.
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function return_loan( $loan_id, $return_note = '' ) {
|
||||||
|
$loan_id = absint( $loan_id );
|
||||||
|
$loan_tab = $this->loans_table();
|
||||||
|
|
||||||
|
if ( $loan_id < 1 || '' === $loan_tab ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$loan = $this->wpdb->get_row( $this->wpdb->prepare( "SELECT * FROM {$loan_tab} WHERE id = %d", $loan_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
|
||||||
|
if ( ! $loan || 'open' !== $loan->status ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = $this->find( (int) $loan->item_id );
|
||||||
|
|
||||||
|
if ( ! $item ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->wpdb->query( 'START TRANSACTION' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
|
||||||
|
$updated_loan = $this->wpdb->update(
|
||||||
|
$loan_tab,
|
||||||
|
array(
|
||||||
|
'status' => 'returned',
|
||||||
|
'returned_at' => $this->now(),
|
||||||
|
'return_note' => $return_note,
|
||||||
|
'updated_at' => $this->now(),
|
||||||
|
),
|
||||||
|
array( 'id' => $loan_id ),
|
||||||
|
array( '%s', '%s', '%s', '%s' ),
|
||||||
|
array( '%d' )
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( false === $updated_loan ) {
|
||||||
|
$this->wpdb->query( 'ROLLBACK' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_available = min( (int) $item->total_quantity, (int) $item->available_quantity + (int) $loan->borrowed_quantity );
|
||||||
|
|
||||||
|
$updated_item = $this->wpdb->update(
|
||||||
|
$this->table,
|
||||||
|
array(
|
||||||
|
'available_quantity' => $new_available,
|
||||||
|
'updated_at' => $this->now(),
|
||||||
|
),
|
||||||
|
array( 'id' => (int) $loan->item_id ),
|
||||||
|
array( '%d', '%s' ),
|
||||||
|
array( '%d' )
|
||||||
|
);
|
||||||
|
|
||||||
|
if ( false === $updated_item ) {
|
||||||
|
$this->wpdb->query( 'ROLLBACK' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->wpdb->query( 'COMMIT' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get open loans with item and borrower information.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_open_loans() {
|
||||||
|
$table = $this->loans_table();
|
||||||
|
|
||||||
|
if ( '' === $table ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT l.*, i.name AS item_name, u.display_name AS borrower_name
|
||||||
|
FROM {$table} l
|
||||||
|
LEFT JOIN {$this->table} i ON i.id = l.item_id
|
||||||
|
LEFT JOIN {$this->wpdb->users} u ON u.ID = l.user_id
|
||||||
|
WHERE l.status = %s
|
||||||
|
ORDER BY l.borrowed_at DESC, l.id DESC";
|
||||||
|
|
||||||
|
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, 'open' ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get latest loan history entries.
|
||||||
|
*
|
||||||
|
* @param int $limit Number of rows.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function get_recent_loans( $limit = 20 ) {
|
||||||
|
$table = $this->loans_table();
|
||||||
|
$limit = max( 1, min( 200, (int) $limit ) );
|
||||||
|
|
||||||
|
if ( '' === $table ) {
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT l.*, i.name AS item_name, u.display_name AS borrower_name
|
||||||
|
FROM {$table} l
|
||||||
|
LEFT JOIN {$this->table} i ON i.id = l.item_id
|
||||||
|
LEFT JOIN {$this->wpdb->users} u ON u.ID = l.user_id
|
||||||
|
ORDER BY l.borrowed_at DESC, l.id DESC
|
||||||
|
LIMIT %d";
|
||||||
|
|
||||||
|
return $this->wpdb->get_results( $this->wpdb->prepare( $sql, $limit ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||||
|
}
|
||||||
|
}
|
||||||
21
includes/Repositories/MeterReadingRepository.php
Normal file → Executable file
21
includes/Repositories/MeterReadingRepository.php
Normal file → Executable file
@@ -244,4 +244,25 @@ class MeterReadingRepository extends AbstractRepository {
|
|||||||
|
|
||||||
return array_reverse( array_values( $monthly ) );
|
return array_reverse( array_values( $monthly ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct an existing reading (value, date, note).
|
||||||
|
*
|
||||||
|
* @param int $id Reading ID.
|
||||||
|
* @param array $data Corrected data: reading_value, reading_date, note.
|
||||||
|
* @return int|false
|
||||||
|
*/
|
||||||
|
public function update_reading( $id, $data ) {
|
||||||
|
return $this->wpdb->update(
|
||||||
|
$this->table,
|
||||||
|
array(
|
||||||
|
'reading_value' => (float) $data['reading_value'],
|
||||||
|
'reading_date' => sanitize_text_field( $data['reading_date'] ),
|
||||||
|
'note' => sanitize_textarea_field( $data['note'] ),
|
||||||
|
),
|
||||||
|
array( 'id' => absint( $id ) ),
|
||||||
|
array( '%f', '%s', '%s' ),
|
||||||
|
array( '%d' )
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
0
includes/Repositories/MeterRepository.php
Normal file → Executable file
0
includes/Repositories/MeterRepository.php
Normal file → Executable file
0
includes/Repositories/ParcelRepository.php
Normal file → Executable file
0
includes/Repositories/ParcelRepository.php
Normal file → Executable file
0
includes/Repositories/SectionRepository.php
Normal file → Executable file
0
includes/Repositories/SectionRepository.php
Normal file → Executable file
0
includes/Repositories/TenantRepository.php
Normal file → Executable file
0
includes/Repositories/TenantRepository.php
Normal file → Executable file
0
includes/Repositories/WorkRepository.php
Normal file → Executable file
0
includes/Repositories/WorkRepository.php
Normal file → Executable file
0
includes/Roles.php
Normal file → Executable file
0
includes/Roles.php
Normal file → Executable file
46
includes/Schema.php
Normal file → Executable file
46
includes/Schema.php
Normal file → Executable file
@@ -27,6 +27,8 @@ class Schema {
|
|||||||
'parcels' => $wpdb->prefix . 'kgvvm_parcels',
|
'parcels' => $wpdb->prefix . 'kgvvm_parcels',
|
||||||
'meters' => $wpdb->prefix . 'kgvvm_meters',
|
'meters' => $wpdb->prefix . 'kgvvm_meters',
|
||||||
'tenants' => $wpdb->prefix . 'kgvvm_tenants',
|
'tenants' => $wpdb->prefix . 'kgvvm_tenants',
|
||||||
|
'inventory_items' => $wpdb->prefix . 'kgvvm_inventory_items',
|
||||||
|
'inventory_loans' => $wpdb->prefix . 'kgvvm_inventory_loans',
|
||||||
'parcel_members' => $wpdb->prefix . 'kgvvm_parcel_members',
|
'parcel_members' => $wpdb->prefix . 'kgvvm_parcel_members',
|
||||||
'parcel_tenants' => $wpdb->prefix . 'kgvvm_parcel_tenants',
|
'parcel_tenants' => $wpdb->prefix . 'kgvvm_parcel_tenants',
|
||||||
'chat_messages' => $wpdb->prefix . 'kgvvm_chat_messages',
|
'chat_messages' => $wpdb->prefix . 'kgvvm_chat_messages',
|
||||||
@@ -126,6 +128,42 @@ class Schema {
|
|||||||
KEY is_active (is_active)
|
KEY is_active (is_active)
|
||||||
) {$charset_collate};";
|
) {$charset_collate};";
|
||||||
|
|
||||||
|
$sql[] = "CREATE TABLE " . self::table( 'inventory_items' ) . " (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
name VARCHAR(190) NOT NULL,
|
||||||
|
total_quantity INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
available_quantity INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
storage_location VARCHAR(190) NULL,
|
||||||
|
description TEXT NULL,
|
||||||
|
is_active TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY name (name),
|
||||||
|
KEY is_active (is_active)
|
||||||
|
) {$charset_collate};";
|
||||||
|
|
||||||
|
$sql[] = "CREATE TABLE " . self::table( 'inventory_loans' ) . " (
|
||||||
|
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
item_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
borrowed_quantity INT UNSIGNED NOT NULL,
|
||||||
|
borrowed_at DATETIME NOT NULL,
|
||||||
|
due_date DATE NULL,
|
||||||
|
returned_at DATETIME NULL,
|
||||||
|
note TEXT NULL,
|
||||||
|
return_note TEXT NULL,
|
||||||
|
status VARCHAR(20) NOT NULL DEFAULT 'open',
|
||||||
|
created_at DATETIME NOT NULL,
|
||||||
|
updated_at DATETIME NOT NULL,
|
||||||
|
PRIMARY KEY (id),
|
||||||
|
KEY item_id (item_id),
|
||||||
|
KEY user_id (user_id),
|
||||||
|
KEY status (status),
|
||||||
|
KEY borrowed_at (borrowed_at),
|
||||||
|
KEY due_date (due_date)
|
||||||
|
) {$charset_collate};";
|
||||||
|
|
||||||
$sql[] = "CREATE TABLE " . self::table( 'parcel_members' ) . " (
|
$sql[] = "CREATE TABLE " . self::table( 'parcel_members' ) . " (
|
||||||
parcel_id BIGINT UNSIGNED NOT NULL,
|
parcel_id BIGINT UNSIGNED NOT NULL,
|
||||||
user_id BIGINT UNSIGNED NOT NULL,
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
@@ -175,6 +213,9 @@ class Schema {
|
|||||||
entry_year SMALLINT UNSIGNED NOT NULL,
|
entry_year SMALLINT UNSIGNED NOT NULL,
|
||||||
power_price_per_kwh DECIMAL(12,4) NULL,
|
power_price_per_kwh DECIMAL(12,4) NULL,
|
||||||
water_price_per_m3 DECIMAL(12,4) NULL,
|
water_price_per_m3 DECIMAL(12,4) NULL,
|
||||||
|
statement_is_locked TINYINT(1) NOT NULL DEFAULT 0,
|
||||||
|
statement_locked_at DATETIME NULL,
|
||||||
|
statement_locked_by BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
created_at DATETIME NOT NULL,
|
created_at DATETIME NOT NULL,
|
||||||
updated_at DATETIME NOT NULL,
|
updated_at DATETIME NOT NULL,
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
@@ -201,6 +242,7 @@ class Schema {
|
|||||||
distribution_type VARCHAR(20) NOT NULL DEFAULT 'parcel',
|
distribution_type VARCHAR(20) NOT NULL DEFAULT 'parcel',
|
||||||
unit_amount DECIMAL(12,2) NULL,
|
unit_amount DECIMAL(12,2) NULL,
|
||||||
total_cost DECIMAL(12,2) NOT NULL DEFAULT 0.00,
|
total_cost DECIMAL(12,2) NOT NULL DEFAULT 0.00,
|
||||||
|
is_mandatory TINYINT(1) NOT NULL DEFAULT 1,
|
||||||
note TEXT NULL,
|
note TEXT NULL,
|
||||||
created_at DATETIME NOT NULL,
|
created_at DATETIME NOT NULL,
|
||||||
updated_at DATETIME NOT NULL,
|
updated_at DATETIME NOT NULL,
|
||||||
@@ -281,6 +323,8 @@ class Schema {
|
|||||||
self::table( 'parcels' ),
|
self::table( 'parcels' ),
|
||||||
self::table( 'meters' ),
|
self::table( 'meters' ),
|
||||||
self::table( 'tenants' ),
|
self::table( 'tenants' ),
|
||||||
|
self::table( 'inventory_items' ),
|
||||||
|
self::table( 'inventory_loans' ),
|
||||||
self::table( 'parcel_members' ),
|
self::table( 'parcel_members' ),
|
||||||
self::table( 'parcel_tenants' ),
|
self::table( 'parcel_tenants' ),
|
||||||
self::table( 'chat_messages' ),
|
self::table( 'chat_messages' ),
|
||||||
@@ -316,6 +360,8 @@ class Schema {
|
|||||||
self::table( 'work_logs' ),
|
self::table( 'work_logs' ),
|
||||||
self::table( 'work_year_config' ),
|
self::table( 'work_year_config' ),
|
||||||
self::table( 'work_jobs' ),
|
self::table( 'work_jobs' ),
|
||||||
|
self::table( 'inventory_loans' ),
|
||||||
|
self::table( 'inventory_items' ),
|
||||||
self::table( 'cost_entries' ),
|
self::table( 'cost_entries' ),
|
||||||
self::table( 'cost_rates' ),
|
self::table( 'cost_rates' ),
|
||||||
self::table( 'cost_years' ),
|
self::table( 'cost_years' ),
|
||||||
|
|||||||
0
includes/Services/ParcelService.php
Normal file → Executable file
0
includes/Services/ParcelService.php
Normal file → Executable file
87
includes/Validator.php
Normal file → Executable file
87
includes/Validator.php
Normal file → Executable file
@@ -259,6 +259,7 @@ class Validator {
|
|||||||
$unit_amount = isset( $data['unit_amount'] ) ? str_replace( ',', '.', wp_unslash( $data['unit_amount'] ) ) : '';
|
$unit_amount = isset( $data['unit_amount'] ) ? str_replace( ',', '.', wp_unslash( $data['unit_amount'] ) ) : '';
|
||||||
$entry_year = $this->sanitize_cost_year( $data );
|
$entry_year = $this->sanitize_cost_year( $data );
|
||||||
$distribution_type = sanitize_key( wp_unslash( isset( $data['distribution_type'] ) ? $data['distribution_type'] : 'parcel' ) );
|
$distribution_type = sanitize_key( wp_unslash( isset( $data['distribution_type'] ) ? $data['distribution_type'] : 'parcel' ) );
|
||||||
|
$is_mandatory = isset( $data['is_mandatory'] ) && '1' === (string) wp_unslash( $data['is_mandatory'] );
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'entry_year' => $entry_year,
|
'entry_year' => $entry_year,
|
||||||
@@ -266,6 +267,7 @@ class Validator {
|
|||||||
'distribution_type' => $distribution_type,
|
'distribution_type' => $distribution_type,
|
||||||
'unit_amount' => '' === trim( (string) $unit_amount ) ? '' : (float) $unit_amount,
|
'unit_amount' => '' === trim( (string) $unit_amount ) ? '' : (float) $unit_amount,
|
||||||
'total_cost' => 0.0,
|
'total_cost' => 0.0,
|
||||||
|
'is_mandatory' => $is_mandatory,
|
||||||
'note' => sanitize_textarea_field( wp_unslash( isset( $data['note'] ) ? $data['note'] : '' ) ),
|
'note' => sanitize_textarea_field( wp_unslash( isset( $data['note'] ) ? $data['note'] : '' ) ),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -365,6 +367,91 @@ class Validator {
|
|||||||
return $errors;
|
return $errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize one inventory item payload.
|
||||||
|
*
|
||||||
|
* @param array $data Raw request data.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function sanitize_inventory_item( $data ) {
|
||||||
|
return array(
|
||||||
|
'name' => sanitize_text_field( wp_unslash( isset( $data['name'] ) ? $data['name'] : '' ) ),
|
||||||
|
'total_quantity' => absint( isset( $data['total_quantity'] ) ? $data['total_quantity'] : 0 ),
|
||||||
|
'available_quantity' => absint( isset( $data['available_quantity'] ) ? $data['available_quantity'] : 0 ),
|
||||||
|
'storage_location' => sanitize_text_field( wp_unslash( isset( $data['storage_location'] ) ? $data['storage_location'] : '' ) ),
|
||||||
|
'description' => sanitize_textarea_field( wp_unslash( isset( $data['description'] ) ? $data['description'] : '' ) ),
|
||||||
|
'is_active' => ! empty( $data['is_active'] ) ? 1 : 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate one inventory item payload.
|
||||||
|
*
|
||||||
|
* @param array $data Sanitized data.
|
||||||
|
* @return \WP_Error
|
||||||
|
*/
|
||||||
|
public function validate_inventory_item( $data ) {
|
||||||
|
$errors = new \WP_Error();
|
||||||
|
|
||||||
|
if ( '' === $data['name'] ) {
|
||||||
|
$errors->add( 'inventory_name_required', __( 'Bitte einen Namen für den Inventargegenstand eingeben.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (int) $data['total_quantity'] < 0 ) {
|
||||||
|
$errors->add( 'inventory_total_invalid', __( 'Die Gesamtmenge muss 0 oder größer sein.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (int) $data['available_quantity'] < 0 ) {
|
||||||
|
$errors->add( 'inventory_available_invalid', __( 'Die verfügbare Menge muss 0 oder größer sein.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (int) $data['available_quantity'] > (int) $data['total_quantity'] ) {
|
||||||
|
$errors->add( 'inventory_available_too_high', __( 'Die verfügbare Menge darf nicht größer als die Gesamtmenge sein.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize one inventory borrow request.
|
||||||
|
*
|
||||||
|
* @param array $data Raw request data.
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function sanitize_inventory_loan( $data ) {
|
||||||
|
return array(
|
||||||
|
'item_id' => absint( isset( $data['item_id'] ) ? $data['item_id'] : 0 ),
|
||||||
|
'user_id' => absint( isset( $data['user_id'] ) ? $data['user_id'] : 0 ),
|
||||||
|
'quantity' => absint( isset( $data['quantity'] ) ? $data['quantity'] : 0 ),
|
||||||
|
'due_date' => $this->normalize_date( isset( $data['due_date'] ) ? $data['due_date'] : '' ),
|
||||||
|
'note' => sanitize_textarea_field( wp_unslash( isset( $data['note'] ) ? $data['note'] : '' ) ),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate one inventory borrow request.
|
||||||
|
*
|
||||||
|
* @param array $data Sanitized data.
|
||||||
|
* @return \WP_Error
|
||||||
|
*/
|
||||||
|
public function validate_inventory_loan( $data ) {
|
||||||
|
$errors = new \WP_Error();
|
||||||
|
|
||||||
|
if ( (int) $data['item_id'] < 1 ) {
|
||||||
|
$errors->add( 'inventory_item_required', __( 'Bitte einen Inventargegenstand auswählen.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (int) $data['user_id'] < 1 ) {
|
||||||
|
$errors->add( 'inventory_user_required', __( 'Bitte ein Mitglied auswählen.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( (int) $data['quantity'] < 1 ) {
|
||||||
|
$errors->add( 'inventory_quantity_required', __( 'Bitte eine gültige Ausleihmenge eingeben.', KGVVM_TEXT_DOMAIN ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize optional date fields to Y-m-d.
|
* Normalize optional date fields to Y-m-d.
|
||||||
*
|
*
|
||||||
|
|||||||
9
kgv-verein-manager.php
Normal file → Executable file
9
kgv-verein-manager.php
Normal file → Executable file
@@ -4,7 +4,7 @@
|
|||||||
* Plugin Name: KGV Vereinsverwaltung
|
* Plugin Name: KGV Vereinsverwaltung
|
||||||
* Plugin URI: https://apex-project.de/
|
* Plugin URI: https://apex-project.de/
|
||||||
* Description: Verwaltung von Sparten, Parzellen, Mitgliedern, Pächtern sowie Wasser- und Stromzählern für Kleingartenvereine.
|
* Description: Verwaltung von Sparten, Parzellen, Mitgliedern, Pächtern sowie Wasser- und Stromzählern für Kleingartenvereine.
|
||||||
* Version: 1.17.0
|
* Version: 1.17.7
|
||||||
* Author: Ronny Grobel
|
* Author: Ronny Grobel
|
||||||
* Author URI: https://apex-project.de/
|
* Author URI: https://apex-project.de/
|
||||||
* License: GPL v2 or later
|
* License: GPL v2 or later
|
||||||
@@ -31,7 +31,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
define( 'KGVVM_VERSION', '1.17.0' );
|
define( 'KGVVM_VERSION', '1.17.8' );
|
||||||
define( 'KGVVM_PLUGIN_FILE', __FILE__ );
|
define( 'KGVVM_PLUGIN_FILE', __FILE__ );
|
||||||
define( 'KGVVM_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
define( 'KGVVM_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||||
define( 'KGVVM_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
define( 'KGVVM_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||||
@@ -45,8 +45,9 @@ register_activation_hook( __FILE__, array( '\\KGV\\VereinManager\\Activator', 'a
|
|||||||
register_deactivation_hook( __FILE__, array( '\\KGV\\VereinManager\\Deactivator', 'deactivate' ) );
|
register_deactivation_hook( __FILE__, array( '\\KGV\\VereinManager\\Deactivator', 'deactivate' ) );
|
||||||
|
|
||||||
add_action(
|
add_action(
|
||||||
'plugins_loaded',
|
'init',
|
||||||
function() {
|
function() {
|
||||||
\KGV\VereinManager\Plugin::instance()->run();
|
\KGV\VereinManager\Plugin::instance()->run();
|
||||||
}
|
},
|
||||||
|
1
|
||||||
);
|
);
|
||||||
|
|||||||
26
readme.txt
Normal file → Executable file
26
readme.txt
Normal file → Executable file
@@ -3,7 +3,7 @@ Contributors: ronnygrobel
|
|||||||
Tags: verein, mitgliederverwaltung, parzellen, zaehler, abrechnung
|
Tags: verein, mitgliederverwaltung, parzellen, zaehler, abrechnung
|
||||||
Requires at least: 6.0
|
Requires at least: 6.0
|
||||||
Tested up to: 6.8
|
Tested up to: 6.8
|
||||||
Stable tag: 1.17.0
|
Stable tag: 1.17.8
|
||||||
Requires PHP: 7.2
|
Requires PHP: 7.2
|
||||||
License: GPLv2 or later
|
License: GPLv2 or later
|
||||||
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
License URI: https://www.gnu.org/licenses/gpl-2.0.html
|
||||||
@@ -41,6 +41,30 @@ Ja, insbesondere fuer Kleingartenvereine und deren Verwaltungsprozesse.
|
|||||||
|
|
||||||
== Changelog ==
|
== Changelog ==
|
||||||
|
|
||||||
|
= 1.17.8 =
|
||||||
|
Feat: Verbrauchsauswertung – Ablesung korrigieren (Datum, Zählerstand, Korrekturnotiz) und löschen direkt aus der Tabelle.
|
||||||
|
|
||||||
|
= 1.17.7 =
|
||||||
|
Feat: Inventarverwaltung – Gegenstände (Werkzeug etc.) erfassen, bearbeiten, löschen. Ausleihe und Rückgabe je Mitglied mit Notiz und Fälligkeitsdatum tracken. Export/Import integriert.
|
||||||
|
|
||||||
|
= 1.17.6 =
|
||||||
|
Feat: Jahresabrechnung kann festgeschrieben (gesperrt) werden. Alle Schreibzugriffe auf Kosten, Preise und Parzellenzuordnungen prüfen den Sperrstatus serverseitig.
|
||||||
|
|
||||||
|
= 1.17.5 =
|
||||||
|
Fix: Spaltenbreiten in Abrechnungstabellen angepasst für bessere Lesbarkeit langer Texte.
|
||||||
|
|
||||||
|
= 1.17.4 =
|
||||||
|
Fix: Checkbox "Verpflichtende Position" bei Kostenposten wird jetzt korrekt gespeichert wenn sie deaktiviert ist.
|
||||||
|
|
||||||
|
= 1.17.3 =
|
||||||
|
Verbesserung: Kostenübersicht zeigt jetzt direkt pro Kostenposten den Status Verpflichtend oder Manuell in einer eigenen Spalte an.
|
||||||
|
|
||||||
|
= 1.17.2 =
|
||||||
|
Fix: Manuelle Kostenpositionen auf der Jahresabrechnung einer Parzelle werden nach dem Hinzufügen jetzt sofort korrekt berücksichtigt. Pflichtpositionen ohne Einschränkung werden in der Seitenleiste als automatisch aktiv dargestellt.
|
||||||
|
|
||||||
|
= 1.17.1 =
|
||||||
|
Feat: is_mandatory Flag für Kostenpositionstypen - Kostenposten können jetzt als "verpflichtend" oder "manuell/optional" gekennzeichnet werden. Checkbox in der Kostenposten-Bearbeitung.
|
||||||
|
|
||||||
= 1.17.0 =
|
= 1.17.0 =
|
||||||
Parzellenspezifische Kostenpositionenzuweisung: Kostenposten können jetzt einzelnen Parzellen zugeordnet werden (z.B. Versicherung nur für bestimmte Parzellen). Editor auf der Jahresabrechnung Parzelle Seite mit übersichtlicher Liste und Hinzufügen/Entfernen-Funktionen. Zuordnungen werden in einer separaten Tabelle gespeichert und sind vollständig in Export/Import integriert.
|
Parzellenspezifische Kostenpositionenzuweisung: Kostenposten können jetzt einzelnen Parzellen zugeordnet werden (z.B. Versicherung nur für bestimmte Parzellen). Editor auf der Jahresabrechnung Parzelle Seite mit übersichtlicher Liste und Hinzufügen/Entfernen-Funktionen. Zuordnungen werden in einer separaten Tabelle gespeichert und sind vollständig in Export/Import integriert.
|
||||||
|
|
||||||
|
|||||||
0
uninstall.php
Normal file → Executable file
0
uninstall.php
Normal file → Executable file
Reference in New Issue
Block a user