]> wagnertech.de Git - SVBaL.git/commitdiff
Merge branch 'master' of http://wagnertech.de/git/SVBaL master
authorMichael Wagner <mail@wagnertech.de>
Thu, 19 Mar 2026 22:24:22 +0000 (23:24 +0100)
committerMichael Wagner <mail@wagnertech.de>
Thu, 19 Mar 2026 22:24:22 +0000 (23:24 +0100)
149 files changed:
COBOL/.dummy [new file with mode: 0644]
COBOL/vereincheck.cbl [new file with mode: 0644]
Mitgliederverwaltung/AWK/.dummy [deleted file]
Mitgliederverwaltung/AWK/MVAdmin.php [deleted file]
Mitgliederverwaltung/AWK/impl/.dummy [deleted file]
Mitgliederverwaltung/AWK/impl/GenadData.php [deleted file]
Mitgliederverwaltung/AWK/impl/data/.dummy [deleted file]
Mitgliederverwaltung/AWK/impl/data/build.properties [deleted file]
Mitgliederverwaltung/AWK/impl/data/runtime-conf.xml [deleted file]
Mitgliederverwaltung/AWK/impl/data/schema.xml [deleted file]
Mitgliederverwaltung/etc/.dummy [deleted file]
Mitgliederverwaltung/gui/.dummy [deleted file]
Mitgliederverwaltung/gui/ConfigurationData.php [deleted file]
Mitgliederverwaltung/gui/index.php [deleted file]
Mitgliederverwaltung/gui/name_map.php [deleted file]
Mitgliederverwaltung/gui/view/.dummy [deleted file]
Mitgliederverwaltung/gui/view/admin.css [deleted symlink]
Mitgliederverwaltung/gui/view/gfx [deleted symlink]
README [new file with mode: 0644]
Test/.dummy [new file with mode: 0644]
Test/FitnessePages/.dummy [new file with mode: 0644]
Test/FitnessePages/DjangoTest/.dummy [new file with mode: 0644]
Test/FitnessePages/DjangoTest/content.txt [new file with mode: 0644]
Test/FitnessePages/DjangoTest/properties.xml [new file with mode: 0644]
Test/FitnessePages/PortalTest/.dummy [new file with mode: 0644]
Test/FitnessePages/PortalTest/content.txt [new file with mode: 0644]
Test/FitnessePages/PortalTest/properties.xml [new file with mode: 0644]
Test/FitnessePages/content.txt [new file with mode: 0644]
Test/FitnessePages/properties.xml [new file with mode: 0644]
Test/bin/.dummy [new file with mode: 0644]
Test/bin/create-test-export [new file with mode: 0755]
Test/etc/.dummy [new file with mode: 0644]
Test/etc/Export.csv [new file with mode: 0644]
Test/etc/TestExport.csv [new file with mode: 0644]
bin/.dummy [new file with mode: 0644]
bin/delete_vorlage.sh [new file with mode: 0755]
bin/ehapp-verein-check.sh [new file with mode: 0755]
bin/install_vorlage.sh [new file with mode: 0755]
bin/pdfersteller.sh [new file with mode: 0755]
debian/.dummy [new file with mode: 0644]
debian/eigenheimer-util.changelog [new file with mode: 0644]
debian/eigenheimer-util.conf [new file with mode: 0644]
debian/eigenheimer-util.control [new file with mode: 0644]
debian/eigenheimer-util.cp [new file with mode: 0755]
debian/eigenheimer-util.postinst [new file with mode: 0755]
doc/.dummy [new file with mode: 0644]
doc/README.Django [new file with mode: 0644]
etc/SVBaL.conf [deleted file]
etc/eh_app.conf [new file with mode: 0644]
latex/.dummy [new file with mode: 0644]
latex/ausweis.tex [new file with mode: 0644]
latex/brief.lco [new file with mode: 0644]
latex/brief.tex [new symlink]
python/.dummy [new file with mode: 0644]
python/eh_app/.dummy [new file with mode: 0644]
python/eh_app/AWK/.dummy [new file with mode: 0644]
python/eh_app/AWK/config.py [new file with mode: 0644]
python/eh_app/AWK/pdf_ersteller.py [new file with mode: 0644]
python/eh_app/AWK/routines.py [new file with mode: 0644]
python/eh_app/AWK/util.py [new file with mode: 0644]
python/eh_app/__init__.py [new file with mode: 0644]
python/eh_app/admin.py [new file with mode: 0644]
python/eh_app/apps.py [new file with mode: 0644]
python/eh_app/forms.py [new file with mode: 0644]
python/eh_app/migrations/.dummy [new file with mode: 0644]
python/eh_app/migrations/0001_initial.py [new file with mode: 0644]
python/eh_app/migrations/__init__.py [new file with mode: 0644]
python/eh_app/models.py [new file with mode: 0644]
python/eh_app/qmodels.py [new file with mode: 0644]
python/eh_app/templates/.dummy [new file with mode: 0644]
python/eh_app/templates/__init__.py [new file with mode: 0644]
python/eh_app/templates/datenquelle_andern.html [new file with mode: 0644]
python/eh_app/templates/eh_app.html [new file with mode: 0644]
python/eh_app/templates/ehmeldung.html [new file with mode: 0644]
python/eh_app/templates/kassenbrief.html [new file with mode: 0644]
python/eh_app/templates/kassenbrief_erfolg.html [new file with mode: 0644]
python/eh_app/templates/kassenbrief_zusammenfassung.html [new file with mode: 0644]
python/eh_app/templates/login.html [new file with mode: 0644]
python/eh_app/templates/mitglieder_auswahlen.html [new file with mode: 0644]
python/eh_app/templates/vorlagen_verwalten.html [new file with mode: 0644]
python/eh_app/tests.py [new file with mode: 0644]
python/eh_app/urls.py [new file with mode: 0644]
python/eh_app/views.py [new file with mode: 0644]
python/eh_util/.dummy [new file with mode: 0644]
python/eh_util/ausweis/.dummy [new file with mode: 0644]
python/eh_util/ausweis/AWK/.dummy [new file with mode: 0644]
python/eh_util/ausweis/AWK/config.py [new file with mode: 0644]
python/eh_util/ausweis/AWK/routines.py [new file with mode: 0644]
python/eh_util/ausweis/__init__.py [new file with mode: 0644]
python/eh_util/ausweis/admin.py [new file with mode: 0644]
python/eh_util/ausweis/apps.py [new file with mode: 0644]
python/eh_util/ausweis/forms.py [new file with mode: 0644]
python/eh_util/ausweis/migrations/.dummy [new file with mode: 0644]
python/eh_util/ausweis/migrations/__init__.py [new file with mode: 0644]
python/eh_util/ausweis/models.py [new file with mode: 0644]
python/eh_util/ausweis/templates/.dummy [new file with mode: 0644]
python/eh_util/ausweis/templates/__init__.py [new file with mode: 0644]
python/eh_util/ausweis/templates/anschreiben.html [new file with mode: 0644]
python/eh_util/ausweis/templates/aus_index.html [new file with mode: 0644]
python/eh_util/ausweis/templates/upload.html [new file with mode: 0644]
python/eh_util/ausweis/tests.py [new file with mode: 0644]
python/eh_util/ausweis/urls.py [new file with mode: 0644]
python/eh_util/ausweis/views.py [new file with mode: 0644]
python/eh_util/eh_abgleich/.dummy [new file with mode: 0644]
python/eh_util/eh_abgleich/AWK/.dummy [new file with mode: 0644]
python/eh_util/eh_abgleich/AWK/SVereinControl.py [new file with mode: 0644]
python/eh_util/eh_abgleich/AWK/__init__.py [new file with mode: 0644]
python/eh_util/eh_abgleich/AWK/ausweis.py [new file with mode: 0755]
python/eh_util/eh_abgleich/AWK/mitglied.py [new file with mode: 0644]
python/eh_util/eh_abgleich/MyTest.py [new file with mode: 0755]
python/eh_util/eh_abgleich/__init__.py [new file with mode: 0644]
python/eh_util/eh_abgleich/admin.py [new file with mode: 0644]
python/eh_util/eh_abgleich/apps.py [new file with mode: 0644]
python/eh_util/eh_abgleich/awk.py [new file with mode: 0644]
python/eh_util/eh_abgleich/forms.py [new file with mode: 0644]
python/eh_util/eh_abgleich/migrations/.dummy [new file with mode: 0644]
python/eh_util/eh_abgleich/migrations/__init__.py [new file with mode: 0644]
python/eh_util/eh_abgleich/models.py [new file with mode: 0644]
python/eh_util/eh_abgleich/templates/.dummy [new file with mode: 0644]
python/eh_util/eh_abgleich/templates/__init__.py [new file with mode: 0644]
python/eh_util/eh_abgleich/templates/index.html [new file with mode: 0644]
python/eh_util/eh_abgleich/templates/list.html [new file with mode: 0644]
python/eh_util/eh_abgleich/templates/upload.html [new file with mode: 0644]
python/eh_util/eh_abgleich/tests.py [new file with mode: 0644]
python/eh_util/eh_abgleich/urls.py [new file with mode: 0644]
python/eh_util/eh_abgleich/views.py [new file with mode: 0644]
python/eh_util/eh_util/.dummy [new file with mode: 0644]
python/eh_util/eh_util/__init__.py [new file with mode: 0644]
python/eh_util/eh_util/forms.py [new file with mode: 0644]
python/eh_util/eh_util/settings.py [new file with mode: 0644]
python/eh_util/eh_util/templates/.dummy [new file with mode: 0644]
python/eh_util/eh_util/templates/__init__.py [new file with mode: 0644]
python/eh_util/eh_util/templates/index.html [new file with mode: 0644]
python/eh_util/eh_util/templates/vbasis.html [new file with mode: 0644]
python/eh_util/eh_util/urls.py [new file with mode: 0644]
python/eh_util/eh_util/views.py [new file with mode: 0644]
python/eh_util/eh_util/wsgi.py [new file with mode: 0644]
python/eh_util/kassenbrief/.dummy [new file with mode: 0644]
python/eh_util/kassenbrief/__init__.py [new file with mode: 0644]
python/eh_util/kassenbrief/admin.py [new file with mode: 0644]
python/eh_util/kassenbrief/apps.py [new file with mode: 0644]
python/eh_util/kassenbrief/migrations/.dummy [new file with mode: 0644]
python/eh_util/kassenbrief/migrations/__init__.py [new file with mode: 0644]
python/eh_util/kassenbrief/models.py [new file with mode: 0644]
python/eh_util/kassenbrief/tests.py [new file with mode: 0644]
python/eh_util/kassenbrief/urls.py [new file with mode: 0644]
python/eh_util/kassenbrief/views.py [new file with mode: 0644]
python/sv-merger/.dummy [new file with mode: 0644]
python/sv-merger/sv-merger.py [new file with mode: 0755]

diff --git a/COBOL/.dummy b/COBOL/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/COBOL/vereincheck.cbl b/COBOL/vereincheck.cbl
new file mode 100644 (file)
index 0000000..84f81cd
--- /dev/null
@@ -0,0 +1,17 @@
+       IDENTIFICATION DIVISION.
+       PROGRAM-ID. vereincheck.
+      ***********************************************************
+      * Comilieren: 
+      * cobc -x vereincheck.cbl ../src/fileutil.o
+      ***********************************************************
+       DATA DIVISION.
+       WORKING-STORAGE SECTION.
+       01 action-par    PIC X VALUE "A".
+       01 data-val      PIC X(9).
+       01 ret-val       PIC 99.
+
+       PROCEDURE DIVISION.
+
+       CALL "fileutil" USING action-par, data-val, ret-val
+       DISPLAY "ret-val: "ret-val
+       .
diff --git a/Mitgliederverwaltung/AWK/.dummy b/Mitgliederverwaltung/AWK/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/Mitgliederverwaltung/AWK/MVAdmin.php b/Mitgliederverwaltung/AWK/MVAdmin.php
deleted file mode 100644 (file)
index d1b89c0..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-<?php
-
-// imports
-require_once 'GenericAdmin/GenericAdmin.php';
-require_once 'util/Config.php';
-
-class MVAdmin extends GenericAdmin {
-
-       function MVAdmin() {
-               // Generic Administration configuration data
-               require 'AWK/impl/GenadData.php';
-               parent::setDscrData($mocd_tab, $atdc_tab);
-       }
-}
diff --git a/Mitgliederverwaltung/AWK/impl/.dummy b/Mitgliederverwaltung/AWK/impl/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/Mitgliederverwaltung/AWK/impl/GenadData.php b/Mitgliederverwaltung/AWK/impl/GenadData.php
deleted file mode 100644 (file)
index ae938b3..0000000
+++ /dev/null
@@ -1,128 +0,0 @@
-<?php
-
-// imports
-require_once 'GenericAdmin/DataTypes/AttrProperty.php';
-
-       // Generic Administration configuration data
-       $mocd_tab = array (
-               1 => array (
-                       'intern' => 'stamm',
-                       'extern' => 'Personenstamm'),
-               2 => array (
-                       'intern' => 'grundstuck',
-                       'extern' => 'Grundstück'),
-               3 => array (
-                       'intern' => 'beitrage',
-                       'extern' => 'Beiträge'),
-//             4 => array (
-//                     'intern' => 'Werkzeug',
-//                     'extern' => 'Werkzeug')
-               );
-
-       $atdc_tab = array (
-
-               // Krimi
-               array (
-                       'intern'  => 'autor',
-                       'extern'  => 'Autor',
-                       'moc'     => 'Krimi',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'titel',
-                       'extern'  => 'Titel',
-                       'moc'     => 'Krimi',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'eigentumer',
-                       'extern'  => 'Eigentümer',
-                       'moc'     => 'Krimi',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'zz_bei',
-                       'extern'  => 'zur_Zeit_bei',
-                       'moc'     => 'Krimi',
-                       'props'   => array (AttrProperty::AP_GET, AttrProperty::AP_SET)),
-               array (
-                       'intern'  => 'kommentar',
-                       'extern'  => 'Kommentar',
-                       'moc'     => 'Krimi',
-                       'props'   => array (AttrProperty::AP_GET, AttrProperty::AP_SET)),
-
-               // Hörbuch
-               array (
-                       'intern'  => 'autor',
-                       'extern'  => 'Autor',
-                       'moc'     => 'Horbuch',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'titel',
-                       'extern'  => 'Titel',
-                       'moc'     => 'Horbuch',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'eigentumer',
-                       'extern'  => 'Eigentümer',
-                       'moc'     => 'Horbuch',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'zz_bei',
-                       'extern'  => 'zur_Zeit_bei',
-                       'moc'     => 'Horbuch',
-                       'props'   => array (AttrProperty::AP_GET, AttrProperty::AP_SET)),
-               array (
-                       'intern'  => 'kommentar',
-                       'extern'  => 'Kommentar',
-                       'moc'     => 'Horbuch',
-                       'props'   => array (AttrProperty::AP_GET, AttrProperty::AP_SET)),
-
-               // Roman
-               array (
-                       'intern'  => 'autor',
-                       'extern'  => 'Autor',
-                       'moc'     => 'Roman',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'titel',
-                       'extern'  => 'Titel',
-                       'moc'     => 'Roman',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'eigentumer',
-                       'extern'  => 'Eigentümer',
-                       'moc'     => 'Roman',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'zz_bei',
-                       'extern'  => 'zur_Zeit_bei',
-                       'moc'     => 'Roman',
-                       'props'   => array (AttrProperty::AP_GET, AttrProperty::AP_SET)),
-               array (
-                       'intern'  => 'kommentar',
-                       'extern'  => 'Kommentar',
-                       'moc'     => 'Roman',
-                       'props'   => array (AttrProperty::AP_GET, AttrProperty::AP_SET)),
-
-               // Werkzeug
-               array (
-                       'intern'  => 'typ',
-                       'extern'  => 'Typ',
-                       'moc'     => 'Werkzeug',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'beschreibung',
-                       'extern'  => 'Beschreibung',
-                       'moc'     => 'Werkzeug',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'eigentumer',
-                       'extern'  => 'Eigentümer',
-                       'moc'     => 'Werkzeug',
-                       'props'   => array (AttrProperty::AP_GET)),
-               array (
-                       'intern'  => 'zz_bei',
-                       'extern'  => 'zur_Zeit_bei',
-                       'moc'     => 'Werkzeug',
-                       'props'   => array (AttrProperty::AP_GET, AttrProperty::AP_SET))
-               );
-
-?>
diff --git a/Mitgliederverwaltung/AWK/impl/data/.dummy b/Mitgliederverwaltung/AWK/impl/data/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/Mitgliederverwaltung/AWK/impl/data/build.properties b/Mitgliederverwaltung/AWK/impl/data/build.properties
deleted file mode 100644 (file)
index 82cc9ed..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-# Database driver
-propel.database = mysql
-
-# Project name
-propel.project = propel
diff --git a/Mitgliederverwaltung/AWK/impl/data/runtime-conf.xml b/Mitgliederverwaltung/AWK/impl/data/runtime-conf.xml
deleted file mode 100644 (file)
index 25eec8f..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<config>
-  <!-- Uncomment this if you have PEAR Log installed -->
-  <!--log>
-    <type>file</type>
-    <name>/tmp/propel.log</name>
-    <ident>AlarmManagement</ident>
-    <level>7</level>
-  </log-->
-  <propel>
-    <datasources default="mkrimi">
-      <datasource id="mkrimi">
-        <adapter>mysql</adapter> <!-- sqlite, mysql, myssql, oracle, or pgsql -->
-        <connection>
-          <dsn>mysql:host=localhost;dbname=mkrimi</dsn>
-          <user>mkrimi</user>
-          <password>mkrimi</password>
-          <settings>
-            <setting id="charset">utf8</setting>
-          </settings>
-        </connection>
-      </datasource>
-    </datasources>
-  </propel>
-</config>
diff --git a/Mitgliederverwaltung/AWK/impl/data/schema.xml b/Mitgliederverwaltung/AWK/impl/data/schema.xml
deleted file mode 100644 (file)
index 8053f2e..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<database name="mkrimi" defaultIdMethod="native">
-       <table name="medium">
-               <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
-               <column name="mod" type="integer" required="true"/>
-               <column name="autor" type="varchar" size="100" required="true"/>
-               <column name="titel" type="varchar" size="100" required="true"/>
-               <column name="eigentumer" type="varchar" size="100" required="true"/>
-               <column name="zz_bei" type="varchar" size="100" required="true"/>
-               <column name="kommentar" type="varchar" size="100" required="true"/>
-               <column name="class_key" type="integer" required="true" inheritance="single">
-                       <inheritance key="1" class="Krimi" extends="Medium"/>
-                       <inheritance key="2" class="Horbuch" extends="Medium"/>
-                       <inheritance key="3" class="Roman" extends="Medium"/>
-               </column>
-       </table>
-       <table name="werkzeug">
-               <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true"/>
-               <column name="mod" type="integer" required="true"/>
-               <column name="typ" type="varchar" size="100" required="true"/>
-               <column name="beschreibung" type="varchar" size="100" required="true"/>
-               <column name="eigentumer" type="varchar" size="100" required="true"/>
-               <column name="zz_bei" type="varchar" size="100" required="true"/>
-       </table>
-</database>
diff --git a/Mitgliederverwaltung/etc/.dummy b/Mitgliederverwaltung/etc/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/Mitgliederverwaltung/gui/.dummy b/Mitgliederverwaltung/gui/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/Mitgliederverwaltung/gui/ConfigurationData.php b/Mitgliederverwaltung/gui/ConfigurationData.php
deleted file mode 100644 (file)
index bb3ef9d..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?php
-$configurationData = array (
-
-       "gui::applications" => array ("Mitgliederverwaltung"), 
-       "autoloader" => array ("ClassLoader", "autoload"),
-       "Mitgliederverwaltung::InterfaceClass" => "MVAdmin",
-       "PropelClassPath"       => "AWK/impl/data/build"
-       );
diff --git a/Mitgliederverwaltung/gui/index.php b/Mitgliederverwaltung/gui/index.php
deleted file mode 100644 (file)
index bc674a8..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-//phpinfo();
-
-// define user
-$user = getenv("USER");
-if ($user == "") putenv("USER=gui");
-
-// includes
-$path = preg_replace("/SVBaL.*/", "SVBaL", __FILE__);
-set_include_path($path . PATH_SEPARATOR . get_include_path());
-require_once 'util/Logger.php';
-require_once 'util/Config.php';
-require_once 'GenericAdmin/gui/control/GenadControl.php';
-
-// load configuration + class loader
-include 'gui/ConfigurationData.php';
-Config::setConfiguration($configurationData);
-
-// Logging
-$logger = Logger::getInstance();
-$requestMethod = $_SERVER["REQUEST_METHOD"];
-$logger->log(__FILE__, $requestMethod);
-$logger->log(__FILE__, "GET-Variabeles:");
-while ( list($key, $value) = each($_GET) ) {
-       $logger->log(__FILE__, $key . " : " . $value);;
-}
-$logger->log(__FILE__, "POST-Variabeles:");
-while ( list($key, $value) = each($_POST) ) {
-       $logger->log(__FILE__, $key . " : " . $value);;
-}
-
-// call implementation
-try {
-       $control = new GenadControl();
-       $control->doIt();
-}
-catch (Exception $e) {
-       $logger->logException(__FILE__, $e);    
-       echo "<html><body>Error occurred!</body></html>";
-}
-
diff --git a/Mitgliederverwaltung/gui/name_map.php b/Mitgliederverwaltung/gui/name_map.php
deleted file mode 100644 (file)
index 54afac8..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-$this->nameMap = array (
-       "Back" => "Zurück",
-       "Class Selection" => "Typ",
-       "Create" => "Anlegen",
-       "List" => "Liste",
-       "Select" => "Auswählen",
-       "Submit changes" => "Änderungen speichern",
-       "zur_Zeit_bei" => "zur Zeit bei"
-);
diff --git a/Mitgliederverwaltung/gui/view/.dummy b/Mitgliederverwaltung/gui/view/.dummy
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/Mitgliederverwaltung/gui/view/admin.css b/Mitgliederverwaltung/gui/view/admin.css
deleted file mode 120000 (symlink)
index e0f1667..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/usr/share/php/GenericAdmin/gui/view/admin.css
\ No newline at end of file
diff --git a/Mitgliederverwaltung/gui/view/gfx b/Mitgliederverwaltung/gui/view/gfx
deleted file mode 120000 (symlink)
index ac9994b..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/usr/share/php/mLohn/gui/view/gfx/
\ No newline at end of file
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..174cd92
--- /dev/null
+++ b/README
@@ -0,0 +1,6 @@
+SVBaL-Repository
+================
+- Erste Versuche mit GenericAdmin, am 10.1.2024 gelöscht.
+10.2.2024
+- Beginn mit Python-Werkzeug für den S-Verein-Deltaabgleich
+
diff --git a/Test/.dummy b/Test/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Test/FitnessePages/.dummy b/Test/FitnessePages/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Test/FitnessePages/DjangoTest/.dummy b/Test/FitnessePages/DjangoTest/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Test/FitnessePages/DjangoTest/content.txt b/Test/FitnessePages/DjangoTest/content.txt
new file mode 100644 (file)
index 0000000..0e8a75e
--- /dev/null
@@ -0,0 +1,4 @@
+!|test.util.CallScript|
+|call script|!-rm -r ~/test |true -!|
+|call script|cp -a /opt/mysite ~/test/|
+|call script|cd ~/test/; ./manage.py test eh_app|
diff --git a/Test/FitnessePages/DjangoTest/properties.xml b/Test/FitnessePages/DjangoTest/properties.xml
new file mode 100644 (file)
index 0000000..e13fdf4
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<properties>
+       <Edit>true</Edit>
+       <Files>true</Files>
+       <Properties>true</Properties>
+       <RecentChanges>true</RecentChanges>
+       <Refactor>true</Refactor>
+       <Search>true</Search>
+       <Test>true</Test>
+       <Versions>true</Versions>
+       <WhereUsed>true</WhereUsed>
+</properties>
diff --git a/Test/FitnessePages/PortalTest/.dummy b/Test/FitnessePages/PortalTest/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Test/FitnessePages/PortalTest/content.txt b/Test/FitnessePages/PortalTest/content.txt
new file mode 100644 (file)
index 0000000..1f78a80
--- /dev/null
@@ -0,0 +1,4 @@
+!|test.util.CallScript|
+|call script|wget -O login.html http://localhost/eh-app/|
+|call script|grep "/mdjangostat/admin/css/base.css" login.html|
+|call script|grep "Siedlerverein-Verwaltung by WagnerTech UG" login.html|
diff --git a/Test/FitnessePages/PortalTest/properties.xml b/Test/FitnessePages/PortalTest/properties.xml
new file mode 100644 (file)
index 0000000..e13fdf4
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<properties>
+       <Edit>true</Edit>
+       <Files>true</Files>
+       <Properties>true</Properties>
+       <RecentChanges>true</RecentChanges>
+       <Refactor>true</Refactor>
+       <Search>true</Search>
+       <Test>true</Test>
+       <Versions>true</Versions>
+       <WhereUsed>true</WhereUsed>
+</properties>
diff --git a/Test/FitnessePages/content.txt b/Test/FitnessePages/content.txt
new file mode 100644 (file)
index 0000000..990fb6f
--- /dev/null
@@ -0,0 +1 @@
+!contents -R2 -g -p -f -h
\ No newline at end of file
diff --git a/Test/FitnessePages/properties.xml b/Test/FitnessePages/properties.xml
new file mode 100644 (file)
index 0000000..3096ff6
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<properties>
+       <Edit/>
+       <Files/>
+       <Help></Help>
+       <Properties/>
+       <RecentChanges/>
+       <Refactor/>
+       <Search/>
+       <Suite/>
+       <Suites></Suites>
+       <Versions/>
+       <WhereUsed/>
+</properties>
diff --git a/Test/bin/.dummy b/Test/bin/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Test/bin/create-test-export b/Test/bin/create-test-export
new file mode 100755 (executable)
index 0000000..05ef991
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+if [ $# -lt 1 ]; then
+       echo "usage: create-test-export INPUT"
+       exit 1
+fi
+head -1 $1 > TestExport.csv
+grep 14962 $1 >> TestExport.csv
+grep 19603 $1 >> TestExport.csv
+grep 76491 $1 >> TestExport.csv
+grep 76441 $1 >> TestExport.csv
+grep 75987 $1 >> TestExport.csv
+grep 75723 $1 >> TestExport.csv
+grep 14965 $1 >> TestExport.csv
+grep 14964 $1 >> TestExport.csv
+grep 76736 $1 >> TestExport.csv
+
+# remove BOM
+sed -i $'1s/^\uFEFF//' TestExport.csv
diff --git a/Test/etc/.dummy b/Test/etc/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/Test/etc/Export.csv b/Test/etc/Export.csv
new file mode 100644 (file)
index 0000000..5819ab7
--- /dev/null
@@ -0,0 +1,10 @@
+"Anrede";"Vorname";"Nachname";"Straße";"Zusatzadresse";"PLZ";"Ort";"Land";"Titel";"Geschlecht";"Familienstand";"Mitglieds-Nr";"Geburtsdatum";"Eintrittsdatum";"Austrittsdatum";"Austrittsgrund";"Zahlungsart";"IBAN";"BIC";"Kontonummer";"Bankleitzahl";"Kreditinstitut";"Kontoinhaber";"Mandatsreferenz";"Debitorenkonto-Nr";"Status";"Branche";"Notfallnummer";"Notfallkontakt";"KommE-Mail_P1";"KommFax_P1";"KommMobil_P1";"KommWeb_P1";"KommTelefon_P1";"KommSkype_P1";"Abteilung_1";"Abteilungseintritt_1";"Abteilungsaustritt_1";"Abteilungsstatus_1";"Abteilungsstatus DFB_1";"Abteilungsaustrittsgrund_1";"Abteilung_2";"Abteilungseintritt_2";"Abteilungsaustritt_2";"Abteilungsstatus_2";"Abteilungsstatus DFB_2";"Abteilungsaustrittsgrund_2";"Beitragsbezeichnung_1_1";"Beitragsstart_1_1";"Beitragsende_1_1";"BeitragBerechnetBis_1_1";"BeitragZahlweise_1_1";"BeitragFälligkeitsdatum_1_1";"BeitragVariabel_1_1";"BeitragFormel_1_1";"BeitragGrundbetrag1_1_1";"BeitragGB1Gesperrt_1_1";"BeitragGrundbetrag2_1_1";"BeitragGB2Gesperrt_1_1";"BeitragGrundbetrag3_1_1";"BeitragGB3Gesperrt_1_1";"BeitragGrundbetrag4_1_1";"BeitragGB4Gesperrt_1_1";"BeitragGrundbetrag5_1_1";"BeitragGB5Gesperrt_1_1";"BeitragZahlweiseGesperrt_1_1";"Freifeldname_1";"Freifeldwert_1";"Freifeldname_2";"Freifeldwert_2";"Freifeldname_3";"Freifeldwert_3";"NotizArt_1";"NotizPrio_1";"NotizBetreff_1";"NotizInhalt_1";"NotizDatum_1"
+Herr;Horsti;Acktu;"Blombergstr. 30";"";81825;München;Deutschland;;;;"14962";25.04.1948;01.01.1998;31.12.2050;;;DE24700202701780047001;HYVEDEMMXXX;"1780047000";"70020270";"UniCredit Bank - HypoVereinsbank";Acktun Horst;"10058";"10253";Aktiv;;;;;"";"";;"4317535";;Standard;01.01.1998;;Aktiv;;;;;;;;;Standard;01.01.1998;;;Jahr;;nein;;;;;;;;;;;;;;"";;"";Zeitung;"Ja";;;;;
+Herr;Horsti;Acktu;"Blombergstr. 30";"";81825;München;Deutschland;;;;"14963";25.04.1948;01.01.1998;31.12.2023;;;DE24700202701780047001;HYVEDEMMXXX;"1780047000";"70020270";"UniCredit Bank - HypoVereinsbank";Acktun Horst;"10058";"10253";Aktiv;;;;;"";"";;"4317535";;Standard;01.01.1998;;Aktiv;;;;;;;;;Standard;01.01.1998;;;Jahr;;nein;;;;;;;;;;;;;;"";;"";Zeitung;"Ja";;;;;
+Herr und Frau;Dr. Michael und Ingrid;Wagner;"Turfstr. 18 a";"";81929;München;Deutschland;;Jur. Person oder sächlich;;"14964";;01.04.2024;;;Lastschrift;DE54700905000004428285;GENODEF1S04;"4428285";"70090500";"Sparda-Bank München";Wagner Dr. Michael und Ingrid;"10255";"10255";Aktiv;;;;;;;;;;;;;;;;Zusatzgrundstück;01.04.2024;;Aktiv;;;;;;;;;;;;;;;;;;;;;;Partnernummer;"76736";VersichertesObjekt;"Gleiwitzer Str. 26, 81929 München";Zeitung;"Nein";;;;;
+Herr und Frau;Dr. Michael und Ingrid;Wagner;"Turfstr. 18 a";"";81929;München;Deutschland;;;;"14965";;01.04.2024;;;Lastschrift;DE54700905000004428285;GENODEF1S04;"4428285";"70090500";"Sparda-Bank München";Wagner Dr. Michael und Ingrid;"10256";"10256";Aktiv;;;;;;;;;;;;;;;;Zusatzgrundstück;01.04.2024;;Aktiv;;;;;;;;;;;;;;;;;;;;;;Partnernummer;"76736";VersichertesObjekt;"Finkenweg 5, 83556 Griesstätt";Zeitung;"Nein";;;;;
+Herr und Frau;Dr. Michael und Ingrid;Wagner;"Turfstr. 18 a";"";81929;München;Deutschland;;;;"76736";25.05.1965;01.12.2012;;;Lastschrift;DE54700905000004428285;GENODEF1S04;"4428285";"70090500";"Sparda-Bank München";Wagner Dr. Michael und Ingrid;"10218";"10218";Aktiv;;;;bonzius@vindelicia.de;"";"";;"99 75 94 94";;Standard;01.12.2012;;Aktiv;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"";VersichertesObjekt;"81929 München, Gleiwitzerstr. 28";Zeitung;"Ja";;;;;
+Frau;Elke;Söller;"Argula-von-Grumbach-Sr. 5";"";92345;Dietfurt;Deutschland;;;;"77912";08.05.1979;01.07.2008;;;;;;;;;;"10193";"10193";Aktiv;;;;;"";"";;"084 64 / 64 21 79";;Standard;01.07.2008;;Aktiv;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"";VersichertesObjekt;"92363 Breitenbrunn, Breitenegg 1 a";Zeitung;"Ja";;;;;
+Frau;Marianne;Söller;"Böhmerbrunnenstr. 27";"";92345;Dietfurt;Deutschland;;;;"76441";;01.01.1995;;;;DE98760520800570713611;BYLADEM1NMA;"570713610";"76052080";"Sparkasse Neumarkt i d OPf-Parsberg";Söllner2 Marianne;"10249";"10249";Aktiv;;;;;;;;;;;;;;;;Zusatzgrundstück;01.01.1995;;Aktiv;;;;;;;;;;;;;;;;;;;;;;Partnernummer;"75987";;"";Zeitung;"Nein";;;;;
+Frau;Marianne;Söller;"Böhmerbrunnenstr. 27";"";92354;Dietfurt;Deutschland;;;;"75987";;01.01.1985;;;;DE98760520800570713611;BYLADEM1NMA;"570713610";"76052080";"Sparkasse Neumarkt i d OPf-Parsberg";Söllner Marianne;"10194";"10194";Aktiv;;;;;"";"";;"084 64 / 15 42";;Standard;01.01.1985;;Aktiv;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"";VersichertesObjekt;"92345 Dietfurt 2, Böhmerbrunnenstr. 21";Zeitung;"Ja";;;;;
+Frau;Marianne;Söller;"Böhmerbrunnenstr. 27";"";92345;Dietfurt;Deutschland;;;;"76491";;01.09.1992;;;;DE98760520800570713611;BYLADEM1NMA;"570713610";"76052080";"Sparkasse Neumarkt i d OPf-Parsberg";Söllner3 Marianne;"10250";"10250";Aktiv;;;;;;;;;;;;;;;;Zusatzgrundstück;01.09.1992;;Aktiv;;;;;;;;;;;;;;;;;;;;;;Partnernummer;"75987";VersichertesObjekt;"92363 Breitbrunn, Tannenweg 6";Zeitung;"Nein";;;;;
diff --git a/Test/etc/TestExport.csv b/Test/etc/TestExport.csv
new file mode 100644 (file)
index 0000000..3948d46
--- /dev/null
@@ -0,0 +1,10 @@
+"Mitglieds-Nr";Nachname;Vorname;Anrede;"Straße";PLZ;Ort;Geburtsdatum;Eintrittsdatum;Austrittsdatum;Abteilungen;Zahlungsart;IBAN;BIC;Kontoinhaber;"Mandatsreferenz";Abweichender Zahler Mitglied;Abweichender Zahler IBAN;E-Mail;Postinfo;VersichertesObjekt;Zeitung
+"14962";Acktun;Horst;Herr;"Blombergstr. 30";81825;München;25.04.1948;01.01.1998;;Standard (01.01.1998 bis -);Lastschrift;DE24 7002 0270 1780 0470 00;HYVEDEMMXXX;Acktun Horst;"10058";;;;Nein;;Ja
+"* 19603";Bachmann;Sonja und Horst;Herr und Frau;"Kampenwandstr. 1a";81671;München;04.12.1954;01.04.2010;31.12.2024;Zusatzgrundstück (01.04.2010 bis 31.12.2024);Lastschrift (AZ: Bachmann, Horst und Sonja);;;;"";Bachmann, Horst und Sonja;DE47 7015 0000 0080 1337 39;;Nein;81671 München, Hachinger Bach-Str. 4 c;Nein
+"76491";Söllner;Marianne;Frau;"Böhmerbrunnenstr. 27";92345;Dietfurt;;01.09.1992;;Zusatzgrundstück (01.09.1992 bis -);Lastschrift (AZ: Söllner, Marianne);;;;"";Söllner, Marianne;DE98 7605 2080 0570 7136 10;;Nein;92363 Breitbrunn, Tannenweg 6;Nein
+"76441";Söllner;Marianne;Frau;"Böhmerbrunnenstr. 27";92345;Dietfurt;;01.01.1995;;Zusatzgrundstück (01.01.1995 bis -);Lastschrift (AZ: Söllner, Marianne);;;;"";Söllner, Marianne;DE98 7605 2080 0570 7136 10;;Nein;;Nein
+"75987";Söllner;Marianne;Frau;"Böhmerbrunnenstr. 27";92354;Dietfurt;;01.01.1985;;Standard (01.01.1985 bis -);Lastschrift;DE98 7605 2080 0570 7136 10;BYLADEM1NMA;Söllner Marianne;"10194";;;;Nein;92345 Dietfurt 2, Böhmerbrunnenstr. 21;Ja
+"75723";Tungl;Inge;Frau;"Sonnwendjochstr. 79";81825;München;;01.01.1960;;Standard (01.01.1960 bis -);Überweisung;;;Tungl Inge;"";;;;Nein;;Ja
+"14965";Wagner;Dr. Michael und Ingrid;;"Turfstr. 18 a";81929;München;;01.04.2024;;Zusatzgrundstück (01.04.2024 bis -);Lastschrift (AZ: Wagner, Dr. Michael und Ingrid);;;;"";Wagner, Dr. Michael und Ingrid;DE54 7009 0500 0004 4282 85;;Nein;Finkenweg 5, 83556 Griesstätt;Nein
+"14964";Wagner;Dr. Michael und Ingrid;Herr und Frau;"Turfstr. 18 a";81929;München;;01.04.2024;;Zusatzgrundstück (01.04.2024 bis -);Lastschrift (AZ: Wagner, Dr. Michael und Ingrid);;;;"";Wagner, Dr. Michael und Ingrid;DE54 7009 0500 0004 4282 85;;Nein;Gleiwitzer Str. 26, 81929 München;Nein
+"76736";Wagner;Dr. Michael und Ingrid;Herr und Frau;"Turfstr. 18 a";81929;München;25.05.1965;01.12.2012;;Standard (01.12.2012 bis -);Lastschrift;DE54 7009 0500 0004 4282 85;GENODEF1S04;Wagner Dr. Michael und Ingrid;"10218";;;bonzius@vindelicia.de;Nein;81929 München, Gleiwitzerstr. 28;Ja
diff --git a/bin/.dummy b/bin/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/bin/delete_vorlage.sh b/bin/delete_vorlage.sh
new file mode 100755 (executable)
index 0000000..1b42d8b
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -e
+
+if [ $# -ne 3 ]
+then
+       echo "invalid call: delete_vorlage.sh VEREIN VORLAGE"
+       exit 7
+fi
+
+verein=$1
+vorlage=$2
+
+echo "Delete $vorlage for $verein"
+rm eh_app/templates/$verein/$vorlage
diff --git a/bin/ehapp-verein-check.sh b/bin/ehapp-verein-check.sh
new file mode 100755 (executable)
index 0000000..4ff238c
--- /dev/null
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+usage="ehapp-verein-check DATA_PATH"
+
+if [ -z "$1" ]; then
+       echo $usage
+       exit 7
+fi
+data_path=$1
+
+mkdir -p $data_path/quellen
+mkdir -p $data_path/upload
+mkdir -p $data_path/build
diff --git a/bin/install_vorlage.sh b/bin/install_vorlage.sh
new file mode 100755 (executable)
index 0000000..9e5ca56
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/bash
+set -e
+
+if [ $# -ne 4 ]
+then
+       echo "invalid call: install.sh VEREIN TGZDATEI WORKDIR"
+       exit 7
+fi
+
+verein=$1
+tgz=$2
+workdir=$3
+vorlage=${2%%.tgz}
+
+echo "Install $vorlage for $verein"
+
+mkdir -p $workdir
+mkdir -p eh_app/templates/$verein
+
+pushd $workdir >/dev/null
+       tar xf $workdir/upload/$2
+popd >/dev/null
+ln -sf $workdir/$vorlage/$vorlage.tex eh_app/templates/$verein/
diff --git a/bin/pdfersteller.sh b/bin/pdfersteller.sh
new file mode 100755 (executable)
index 0000000..f471c5a
--- /dev/null
@@ -0,0 +1,18 @@
+#/bin/bash
+set -e
+
+usage="pdferasteller DIR FILE"
+if [ $# -ne 2 ]; then
+       echo $usage
+       exit 7
+fi
+dir=$1
+file=$2
+ergebnis_dir="ergebnis/"
+
+cd $dir
+pdflatex $file
+
+# Schiebe Ergebnis in das Ergebnisverzeichnis
+erg_datei=${file%.tex}.pdf
+mv $erg_datei $ergebnis_dir
diff --git a/debian/.dummy b/debian/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/debian/eigenheimer-util.changelog b/debian/eigenheimer-util.changelog
new file mode 100644 (file)
index 0000000..b27984b
--- /dev/null
@@ -0,0 +1,4 @@
+svbal (0.1-%BUILD%) unstable; urgency=medium
+  * initial version: Ausweiserstellung, Kassenbrief
+ -- Michael Wagner <info@wagnertech.de>  Sat, 06 Jun 2020 20:03:04 +0100
+
diff --git a/debian/eigenheimer-util.conf b/debian/eigenheimer-util.conf
new file mode 100644 (file)
index 0000000..4f91cd7
--- /dev/null
@@ -0,0 +1,2 @@
+compile_type=NONE
+target_type=DEB
diff --git a/debian/eigenheimer-util.control b/debian/eigenheimer-util.control
new file mode 100644 (file)
index 0000000..138b18e
--- /dev/null
@@ -0,0 +1,12 @@
+Source: svbal
+Section: main
+Priority: optional
+Maintainer: Michael Wagner <michael@wagnertech.de>
+Build-Depends: git, mbuild
+Package: eigenheimer-util
+Architecture: all
+Depends: texlive-latex-base, texlive-latex-recommended, texlive-lang-german, texlive-latex-extra, python3, apache2, libapache2-mod-wsgi-py3, python3-django
+Description: Mitgliederverwaltung Eigenheimerverband
+ .
+
diff --git a/debian/eigenheimer-util.cp b/debian/eigenheimer-util.cp
new file mode 100755 (executable)
index 0000000..adcff7b
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e
+
+mkdir -p $1/opt
+cp -r python/eh_app $1/opt/
+rm -r $1/opt/eh_app/templates/svbal 2>/dev/null |true
+
+mkdir -p $1/opt/eh_app/bin
+cp bin/* $1/opt/eh_app/bin/
diff --git a/debian/eigenheimer-util.postinst b/debian/eigenheimer-util.postinst
new file mode 100755 (executable)
index 0000000..33b88b4
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/bash
+set -e
+
+ln -sf /opt/eh_app /opt/mysite/
+
+# add application to settings.py
+if ! grep "### EHUTIL ###" /opt/mysite/mysite/settings.py >/dev/null
+then
+       echo "add ehutil settings to settings.py"
+       cat << EOF >> /opt/mysite/mysite/settings.py
+### EHUTIL ###
+
+INSTALLED_APPS.append('eh_app.apps.EhAppConfig')
+
+### EHUTIL-END ###
+EOF
+fi
+
+# DB anlegen/migrieren
+/opt/mysite/manage.py migrate
+
+# add url routing
+if ! grep "### EHUTIL ###" /opt/mysite/mysite/urls.py >/dev/null
+then
+       echo "add url routing"
+       cat << EOF >> /opt/mysite/mysite/urls.py
+### EHUTIL ###
+
+from django.urls import include
+urlpatterns.append(path('eh-app/', include('eh_app.urls')))
+
+### EHUTIL-END ###
+EOF
+fi
+
+# prepare template dir
+chown www-data:www-data /opt/eh_app/templates
+
+# restart apache
+systemctl restart apache2
diff --git a/doc/.dummy b/doc/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/doc/README.Django b/doc/README.Django
new file mode 100644 (file)
index 0000000..7044dea
--- /dev/null
@@ -0,0 +1,16 @@
+Django
+======
+
+- Version
+python3 -m django --version
+
+- Projekt/Anwendung
+django-admin startproject mysite
+
+- Server starten
+manage.py runserver
+
+- Model-View-Control
+ x Model   -> awk.py
+ x View    -> templates + forms.py
+ x Control -> views.py
diff --git a/etc/SVBaL.conf b/etc/SVBaL.conf
deleted file mode 100644 (file)
index b9909e2..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-# Configuration for mKrimi Verleihnix Gui
-   Alias /SVBaL "/usr/share/php/SVBaL/gui"
-   <Directory /usr/share/php/SVBaL/gui>
-      Options +FollowSymlinks
-      RewriteEngine On
-      RewriteBase /SVBaL/
-      RewriteRule ^.*php$ index.php
-   </Directory>
-
diff --git a/etc/eh_app.conf b/etc/eh_app.conf
new file mode 100644 (file)
index 0000000..4b89735
--- /dev/null
@@ -0,0 +1 @@
+WSGIScriptAlias /eh-app /opt/mysite/mysite/wsgi.py
diff --git a/latex/.dummy b/latex/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/latex/ausweis.tex b/latex/ausweis.tex
new file mode 100644 (file)
index 0000000..90295fe
--- /dev/null
@@ -0,0 +1,17 @@
+\documentclass{scrlttr2}
+\LoadLetterOption{ausweis}
+\setkomavar{date}{\vspace*{-6\baselineskip}\today}
+\begin{document}
+\begin{letter}{
+       %\today \par \vspace*{2\baselineskip}
+       \vspace*{4\baselineskip}[[Testdruck]]\\Michael Wagner\\Turfstr. 18a\\81929 München
+}
+\opening{\par \vspace*{3\baselineskip}Sehr geehrter Herr Wagner,}
+Hier steht der Text
+
+in mehreren Absätzen SDFG SDF SDFG SDFGHSDFGH SDFGH SDFGH SDF SDFG SDF SDSGH SDFG HGFTHs dfgsdf sdfg sdfg sdf sdfg dfsg sdfg s.
+
+Und ein dritter.
+\closing{Mit freundlichen Grüßen}
+\end{letter}
+\end{document}
diff --git a/latex/brief.lco b/latex/brief.lco
new file mode 100644 (file)
index 0000000..91817ba
--- /dev/null
@@ -0,0 +1,55 @@
+\ProvidesFile{testlco.lco}[2022/01/08 letter class option]
+\RequirePackage{ngerman}                % Deutsche Sprache
+\RequirePackage{fix-cm}
+\RequirePackage[T1]{fontenc}            % Ligaturschriften
+\RequirePackage[utf8]{inputenc}      % Eingabezeichensatz
+\RequirePackage{graphicx}               % Grafikpaket
+\RequirePackage{hyperref}
+\usepackage{wallpaper} % Hintergrundbilder
+\hypersetup{pdfpagemode=None}
+\KOMAoptions{
+        paper=a4,
+        pagenumber=false,
+%        fromalign=right,
+%        fromrule=afteraddress,
+%        fromphone,
+%       fromfax,
+        fromlogo,
+%        fromurl,fromemail,
+%        backaddress,
+        foldmarks,
+%        headsepline,footsepline,
+        headsepline=true,    % Linie unter dem Header der Folgeseite
+        enlargefirstpage,
+        parskip=half
+}
+\setkomavar{fromname}{SV Berg am Laim}
+\setkomavar{fromaddress}{Turfstr. 18a\\81929~München}
+\setkomavar{firsthead}{}
+%\setkomavar{fromphone}{+49\,(0)\,555\,12345}
+%\setkomavar{fromfax}{+49\,(0)\,555\,12345}
+%\setkomavar{fromemail}{joachim@example.org}
+\setkomavar*{emailseparator}{mailto}
+\setkomavar{emailseparator}{:}
+%\setkomavar{fromurl}{\url{//www.schlosser.info}}
+\setkomavar*{urlseparator}{}
+\setkomavar{urlseparator}{}
+\setkomavar{frombank}{Eine Bank\\IBAN~123\,45\,678}
+\setkomavar{place}{München}
+\setkomavar{signature}{Michael Wagner\\1.Vorsitzender}
+%\setkomavar{fromlogo}{\parbox[b]{8cm}{\usekomafont{fromaddress}%
+%        {\mbox{\LARGE \bfseries Dr.\ rer.\ nat.\ Joachim~Schlosser}}
+%        \smallskip}
+%}
+\setkomafont{backaddress}{\sffamily}
+\setkomafont{fromaddress}{\sffamily}
+%\setkomavar{fromlogo}{\parbox[b]{8cm}{\includegraphics[width=2cm]{logo.jpeg}}}
+\setkomavar{fromlogo}{\includegraphics[width=2cm]{logo.jpeg}}
+\setkomavar{nexthead}{\mbox{\textbf{Siedlervereinigung Berg am Laim}} \usekomavar{fromlogo}}
+\headheight=2cm
+\setkomafont{fromname}{\sffamily}
+\addtokomafont{fromaddress}{\scriptsize}
+\pagestyle{headings}
+%\CenterWallPaper{1}{MitgliederausweisSVBaLVorlage_leer_v2.pdf}
+\CenterWallPaper{1}{../Aktuell/BriefpapierSVBaLVorlage.pdf}
+
diff --git a/latex/brief.tex b/latex/brief.tex
new file mode 120000 (symlink)
index 0000000..6794f2f
--- /dev/null
@@ -0,0 +1 @@
+brief_202507.tex
\ No newline at end of file
diff --git a/python/.dummy b/python/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_app/.dummy b/python/eh_app/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_app/AWK/.dummy b/python/eh_app/AWK/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_app/AWK/config.py b/python/eh_app/AWK/config.py
new file mode 100644 (file)
index 0000000..9ebd559
--- /dev/null
@@ -0,0 +1,54 @@
+'''
+Created on 02.08.2024
+
+@author: sparky2021
+'''
+from eh_app.models import ConfigData
+
+the_instance = None
+
+class Config:
+    '''
+    Singleton Klasse für Konfiguration
+    '''
+
+    def __init__(self, verein):
+        self.verein = verein
+        
+    def getConfig(self, key, default=None):
+        data = ConfigData.objects.filter(verein=self.verein, key=key)
+        if data:
+            return data[0].value
+        return default
+    
+    def requireConfig(self, key):
+        data = self.getConfig(key)
+        if not data:
+            raise RuntimeError(f"Kein Eintag für: {self.verein}/{key}")
+        return data
+        
+    def setConfig(self, key, value=None):
+        # No value supplied: delete entry
+        # check, if entry already exists
+        data = ConfigData.objects.filter(verein=self.verein, key=key)
+        if data:
+            # check data set
+            if len(data) != 1:
+                raise RuntimeError("config entry not unique")
+            if value:
+                if data[0].value != value:
+                    data[0].value = value
+                    data[0].save()
+            else:
+                data[0].delete()
+        elif value:
+            data = ConfigData(verein=self.verein, key=key, value=value)
+            data.save()
+        
+def getInstance(verein=None):
+    global the_instance
+    if not the_instance:
+        if not verein:
+            raise RuntimeError("Bei der ersten Instanzierung muss der Verein mitgegeben werden.")
+        the_instance = Config(verein)
+    return the_instance
diff --git a/python/eh_app/AWK/pdf_ersteller.py b/python/eh_app/AWK/pdf_ersteller.py
new file mode 100644 (file)
index 0000000..9f673e9
--- /dev/null
@@ -0,0 +1,68 @@
+import os
+
+class PdfErsteller(object):
+    '''
+    Klasse zur Erstellung von PDFs
+    Diese Klasse verwaltet Resourcen. Mit "with" verwenden!
+    '''
+
+    def __init__(self, request, template, basisbtr: int, zusatzbtr: int, pfad, pdfersteller):
+        self.request        = request
+        self.template       = template
+        self.pfad           = pfad
+        self.pdfersteller   = pdfersteller
+        self.basisbtr       = basisbtr
+        self.zusatzbtr      = zusatzbtr
+        self.gesamtbetrag   = 0
+        self.anzahl         = 0
+        self.csvfile        = open(os.path.join(pfad, "ergebnis", "rechnungen.csv"), "w")
+        print("Name", "Vorname", "Straße", "PLZ", "Ort", "Anz. Grundstück", "Betrag", sep=";", file=self.csvfile )
+        
+    def __enter__(self):
+        return self
+    
+    def __exit__(self, exc_type, exc_value, traceback):
+        print ("Gesamt", self.gesamtbetrag, sep=";", file=self.csvfile )
+        self.csvfile.close()
+        
+    def erstellepdf(self, m):
+        gg      = m.VersicherteGrundstucke()
+        g1      = gg[0]
+        gn      = gg[1:]
+        beitrag = self.basisbtr + self.zusatzbtr*len(gn)
+        konto   = None
+        if len(m.iban) > 0:
+            konto = m.iban[0:4]+" xxxx xxxx xxxx "+m.iban[-2:]
+        
+        # Hinweise zur Templateerstellung:
+        # https://docs.djangoproject.com/en/2.2/ref/templates/builtins/#ref-templates-builtins-tags
+        
+        tex_bearbeitet = self.template.render(
+            {"vorname"      : m.vorname, 
+             "nachname"     : m.nachname,
+             "strasse"      : m.strasse,
+             "plz"          : m.plz,
+             "ort"          : m.ort,
+             "anrede"       : m.anrede,
+             "grundstuck1"  : g1,
+             "basisbeitrag" : self.basisbtr,
+             "g_weitere"    : gn,
+             "zusatzbeitrag": self.zusatzbtr,
+             "beitrag"      : beitrag,
+             "konto"        : konto,
+             "m_nr"         : m.mitgliedsnr
+            }, self.request)
+    
+        with open(f'{self.pfad}/{m.nachname}_{m.mitgliedsnr}_brief.tex', 'w') as f:
+            f.write(tex_bearbeitet) # schreibt in dieses neue Dokument
+            
+        # AUfruf PDF latex tex -> pdf
+        import subprocess
+        rc = subprocess.call(["bash", self.pdfersteller, self.pfad, f'{m.nachname}_{m.mitgliedsnr}_brief.tex'])
+        if rc != 0:
+            raise RuntimeError(f"pdfersteller.sh failed: {rc}")
+        
+        # schreibe Eintrag in Ergebnisdatei und erhöhe Gesamzbetrag
+        print(m.nachname, m.vorname, m.strasse, m.plz, m.ort, len(gg), beitrag, sep=";", file=self.csvfile )
+        self.gesamtbetrag += beitrag
+        self.anzahl       += 1
diff --git a/python/eh_app/AWK/routines.py b/python/eh_app/AWK/routines.py
new file mode 100644 (file)
index 0000000..6f76b2b
--- /dev/null
@@ -0,0 +1,291 @@
+import os
+import time
+
+from eh_app.AWK import config
+
+'''
+def handle_uploaded_file(csv_file):
+    csv_file_name = str(csv_file)
+    data_path = config.getInstance().requireConfig("data_path")
+    path = os.path.join(data_path, csv_file_name)
+    with open(path, 'wb+') as destination:
+        for chunk in csv_file.chunks():
+            destination.write(chunk)
+'''
+from django.utils.datetime_safe import strftime
+def aktualisiere_config(config, vorlage, beitrag_data):
+    '''
+    if file:
+        uploaded_file = file.name
+        data_path = config.requireConfig("data_path")
+        # copy briefpapier into data_path
+        with open(os.path.join(data_path, uploaded_file), 'wb+') as destination:
+            for chunk in file.chunks():
+                destination.write(chunk)
+        config.setConfig("briefpapier", uploaded_file)
+    '''
+    if vorlage:
+        config.setConfig("vorlage", vorlage)
+    if beitrag_data["basisbeitrag"]:
+        config.setConfig("beitrag_basis", beitrag_data["basisbeitrag"])
+
+    if beitrag_data["zusatzbeitrag"]:
+        config.setConfig("beitrag_zusatz", beitrag_data["zusatzbeitrag"])
+    
+def erstelle_ehmeldung(data):
+    from PyPDF2 import PdfFileWriter, PdfFileReader
+    from datetime import date
+    import io
+    from reportlab.pdfgen import canvas
+    from reportlab.lib.pagesizes import letter
+   
+    packet = io.BytesIO()
+    can = canvas.Canvas(packet, pagesize=letter)
+    
+    # Adressfeld
+    can.setFont("Helvetica", 14)
+    can.drawString(80, 770, "Siedlervereinigung Berg am Laim")
+    can.setFont("Helvetica", 12)
+    can.drawString(205, 618, data["VorZuname"])
+    can.drawString(205, 600, data["VorZunamePartner"])
+    can.drawString(205, 583, data["Wohnanschrift"])
+    can.drawString(205, 566, data["Telefon"])
+    can.drawString(365, 566, data["Email"])
+    can.drawString(205, 549, data["Geburtsdatum"])
+    versichertes_object = data["VersichertesObjekt"]
+    if versichertes_object == "":
+        versichertes_object = data["Wohnanschrift"]
+    can.drawString(205, 500, versichertes_object)
+    can.drawString(193, 466, str(data["AnzahlWohnungen"]))
+    if data["Selbstgenutzt"]:
+        can.drawString(360, 466, "X")
+    if data["Eigentumswohnung"]:
+        can.drawString(192, 449, "X")
+    if data["Gewerblich"]:
+        can.drawString(360, 449, "X")
+    can.drawString(140, 376, date.today().strftime('%d.%m.%Y'))
+    can.drawString(350, 376, "Maschinell erstellt.")
+        
+    can.save()
+    
+    #move to the beginning of the StringIO buffer
+    packet.seek(0)
+    
+    # create a new PDF with Reportlab
+    new_pdf = PdfFileReader(packet)
+    # read your existing PDF
+    existing_pdf = PdfFileReader(open("/home/sparky2021/SVBaL/Aktuell/BeitrittserklarungSVBaL.pdf", "rb"))
+    output = PdfFileWriter()
+    # add the "watermark" (which is the new pdf) on the existing page
+    page = existing_pdf.pages[0]
+    page.mergePage(new_pdf.pages[0])
+    output.addPage(page)
+    # finally, write "output" to a real file
+    output_stream = open("meldung.pdf", "wb")
+    output.write(output_stream)
+    output_stream.close()
+
+def erstelle_ausweis(data):
+    from PyPDF2 import PdfFileWriter, PdfFileReader
+    from datetime import date
+    import io
+    from reportlab.pdfgen import canvas
+    from reportlab.lib.pagesizes import letter
+    
+    packet = io.BytesIO()
+    can = canvas.Canvas(packet, pagesize=letter)
+    
+    # Adressfeld
+    can.setFont("Helvetica", 12)
+    can.drawString(70, 640, "Herr/Frau")
+    can.drawString(70, 625, data["VorZuname"])
+    can.drawString(70, 610, data["Wohnanschrift"])
+    can.drawString(70, 590, "PLZ/Ort")
+
+    # Datum
+    can.setFont("Helvetica", 9)
+    can.drawString(350, 520, "München, "+date.today().strftime('%d.%m.%Y'))
+    
+    # Anschreiben
+    can.drawString(70, 500, "Sehr geehrte/r Herr/Frau"+data["VorZuname"]+",")
+    text = can.beginText(70, 480)
+    text.textLines("Hier Ihr Ausweis")
+    can.drawText(text)
+    
+    
+    can.line(20, 180, 560, 180)
+    
+    # Ausweis
+    can.setFont("Helvetica", 12)
+    can.drawString(460, 100, "#0817654")
+    can.drawString(330, 80, data["VorZuname"])
+    can.setFont("Helvetica", 9)
+    can.drawString(400, 56, data["Geburtsdatum"])
+    can.drawString(400, 42, date.today().strftime('%d.%m.%Y'))
+    versichertes_object = data["VersichertesObjekt"]
+    if versichertes_object == "":
+        versichertes_object = data["Wohnanschrift"]
+    can.drawString(400, 20, versichertes_object)
+    
+    can.save()
+    
+    #move to the beginning of the StringIO buffer
+    packet.seek(0)
+    
+    # create a new PDF with Reportlab
+    new_pdf = PdfFileReader(packet)
+    # read your existing PDF
+    existing_pdf = PdfFileReader(open("MitgliederausweisSVBaLVorlage_leer.pdf", "rb"))
+    output = PdfFileWriter()
+    # add the "watermark" (which is the new pdf) on the existing page
+    page = existing_pdf.pages[0]
+    page.mergePage(new_pdf.pages[0])
+    output.addPage(page)
+    # finally, write "output" to a real file
+    output_stream = open("ausweis.pdf", "wb")
+    output.write(output_stream)
+    output_stream.close()
+      
+def pdfs_erstellen(request, verein, mitglieder, templ_name, data_path, build_name):
+    from django.template import loader
+    
+    # lese alle Miglieder
+    from eh_app.qmodels import Mitglied
+    if mitglieder == "alle":
+        mm = Mitglied.objects.all()
+    else:
+        mm = []
+        m_ids = mitglieder.split(",")
+        for m_id in m_ids:
+            o = Mitglied.objects
+            m = o.get(mitgliedsnr=m_id)
+            if not m:
+                raise RuntimeError(f"Mitglied mit id {m_id} konnte nicht geladen werden.")
+            mm.append(m)
+    template = loader.get_template(f"{verein}/{templ_name}")
+
+    cf = config.getInstance()
+    ehappdir  = cf.getConfig("ehappdir", "/opt/eh_app")
+    pdfersteller = f"{ehappdir}/bin/pdfersteller.sh"
+    if not os.path.exists(pdfersteller):
+        raise RuntimeError("Install-Skript nicht gefunden: "+pdfersteller)
+    
+    from eh_app.AWK.pdf_ersteller import PdfErsteller
+    build_base      = os.path.join(data_path, "build")
+    build_path      = os.path.join(build_base, build_name)
+    result_path     = os.path.join(build_path, "ergebnis")
+    tpl_name_wo_ext = os.path.splitext(templ_name)[0]
+    template_path   = os.path.join(data_path, f"{tpl_name_wo_ext}")
+
+    # alte builds löschen
+    rc = os.system(f'cd {build_base}; rm -r *')
+    if rc != 0:
+        from eh_app.AWK import util
+        util.schreibe_log(f"Löschen alter builds fehlgeschlagen: {rc}")
+    
+    # lege Verzeichnis + Links auf Templatedateien an
+    os.makedirs(result_path)
+    rc = os.system(f'cd {build_path}; ln -s {template_path}/* .')
+    if rc != 0:
+        raise RuntimeError(f"Linkerstellung fehlgeschlagen: {rc}")
+    
+    basisbtr  = int(cf.requireConfig("beitrag_basis"))
+    zusatzbtr = int(cf.requireConfig("beitrag_zusatz"))
+
+    with PdfErsteller(request, template, basisbtr, zusatzbtr, build_path, pdfersteller) as pe:
+    
+        # Erstelle PDFs
+        for m in mm:
+            pe.erstellepdf(m)
+    
+    # Ergebnis packen
+    rc = os.system(f'cd {build_path}; tar czf ergebnis.tgz ergebnis')
+    if rc != 0:
+        raise RuntimeError(f"Packen (tar) fehlgeschlagen: {rc}")
+    
+    return pe.anzahl, pe.gesamtbetrag
+    
+def vertragsliste_erstellen(verein, data_path):
+    build_path     = os.path.join(data_path, "build")
+    ergebnis_datei = "vertragsliste_"+time.strftime("%Y%m%d%H%M%S", time.localtime())+".csv"
+
+    # lese alle Miglieder
+    from eh_app.qmodels import Mitglied
+    mm = Mitglied.objects.all()
+    
+    with open(os.path.join(build_path,ergebnis_datei), "w") as d:
+        print("Name", "Vorname", "Straße", "PLZ", "Ort", "Grundstück", "Eintrittsdatum", sep=";", file=d )
+        for m in mm:
+            gg = m.VersicherteGrundstucke()
+            for g in gg:
+                print(m.nachname, 
+                      m.vorname, 
+                      m.strasse, 
+                      m.plz, 
+                      m.ort, 
+                      g, 
+                      m.eintrittsdatum.strftime("%d.%m.%Y"), 
+                      sep=";", file=d )
+    return ergebnis_datei
+
+def vorlagen_verwalten(verein, zu_loschen, vorlage_neu):
+    import subprocess
+    cf = config.getInstance(verein)
+    ehappdir  = cf.getConfig("ehappdir", "/opt/eh_app")
+    workdir   = cf.getConfig("data_path", "/var/mysite/eh_app")
+    djangodir = cf.getConfig("djangodir", "/opt/mysite")
+    install_sh  = f"{ehappdir}/bin/install_vorlage.sh"
+    delete_sh   = f"{ehappdir}/bin/delete_vorlage.sh"
+    ehapp_check = f"{ehappdir}/bin/ehapp-verein-check.sh"
+    
+    # prüfe Existenz der Skripte
+    if not os.path.exists(install_sh):
+        raise RuntimeError("Install-Skript nicht gefunden: "+install_sh)
+    if not os.path.exists(delete_sh):
+        raise RuntimeError("Delete-Skript nicht gefunden: "+delete_sh)
+    if not os.path.exists(ehapp_check):
+        raise RuntimeError("Check-Skript nicht gefunden: "+ehapp_check)
+    
+    # prüfe Dateistruktur
+    rc = subprocess.call(["bash", ehapp_check, workdir, verein])
+    if rc != 0:
+        raise RuntimeError(f"install_sh failed: {rc}")
+    
+    
+    for vorlage in zu_loschen:
+        rc = os.system(f'{delete_sh} {verein} {vorlage} {ehappdir}')
+        if rc != 0:
+            raise RuntimeError(f"delete_sh fehlgeschlagen: {rc}")
+    if vorlage_neu:
+        vorlage_name = str(vorlage_neu)
+        path = os.path.join(workdir, "upload", vorlage_name)
+        with open(path, 'wb+') as destination:
+            for chunk in vorlage_neu.chunks():
+                destination.write(chunk)
+        #raise RuntimeError(f"xxxxxxxx: {install_sh} {verein} {vorlage_name} {workdir} {ehappdir}")
+        rc = subprocess.call(["bash", install_sh, verein,  vorlage_name, workdir, ehappdir])
+        if rc != 0:
+            raise RuntimeError(f"install_sh failed: {rc}")
+
+def datenquelle_andern(verein, pfad, zu_loschen, zu_verwenden, zu_install):
+    cf         = config.getInstance(verein)
+    akt_quelle = cf.getConfig("datenquelle", "")
+    
+    # Dateien löschen
+    for file in zu_loschen:
+        dpath = os.path.join(pfad, file)
+        os.unlink(dpath)
+        if akt_quelle == file:
+            # Datenquelle zurücksetzen
+            cf.setConfig("datenquelle")
+    
+    # Neue Datei verwenden
+    if zu_install:
+        datei_name = str(zu_install)
+        dpath = os.path.join(pfad, datei_name)
+        with open(dpath, 'wb+') as destination:
+            for chunk in zu_install.chunks():
+                destination.write(chunk)
+        cf.setConfig("datenquelle", datei_name)
+    elif zu_verwenden:
+        cf.setConfig("datenquelle", zu_verwenden)
diff --git a/python/eh_app/AWK/util.py b/python/eh_app/AWK/util.py
new file mode 100644 (file)
index 0000000..b0fc371
--- /dev/null
@@ -0,0 +1,10 @@
+'''
+Utilities für EHUtil
+'''
+import syslog
+from http import cookies
+
+def schreibe_log(eintrag):
+   syslog.openlog("eh_app")
+   syslog.syslog(eintrag) 
\ No newline at end of file
diff --git a/python/eh_app/__init__.py b/python/eh_app/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_app/admin.py b/python/eh_app/admin.py
new file mode 100644 (file)
index 0000000..d654f08
--- /dev/null
@@ -0,0 +1,9 @@
+from django.contrib import admin
+
+# Register your models here.
+
+from django.contrib import admin
+
+from .models import ConfigData
+
+admin.site.register(ConfigData)
diff --git a/python/eh_app/apps.py b/python/eh_app/apps.py
new file mode 100644 (file)
index 0000000..0d52b41
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class EhAppConfig(AppConfig):
+    name = 'eh_app'
diff --git a/python/eh_app/forms.py b/python/eh_app/forms.py
new file mode 100644 (file)
index 0000000..671c9bb
--- /dev/null
@@ -0,0 +1,138 @@
+from django import forms
+'''
+class UploadFileForm(forms.Form):
+    file1 = forms.FileField(label='S-Verein-Export')
+    
+class DocumentForm(forms.Form):
+    docfile = forms.FileField(
+        label='Select a file',
+        help_text='max. 42 megabytes'
+    )
+
+class TextInputForm(forms.Form):
+    text = forms.CharField(
+        label = "Text für das Anschreiben",
+        empty_value = "Bitte Text eingeben ...",
+        widget=forms.Textarea,
+    )
+'''
+from django.db.models.fields import BooleanField
+'''
+- Doku zu Forms:
+https://docs.djangoproject.com/en/2.2/topics/forms/
+- Doku zu Fields:
+https://docs.djangoproject.com/en/2.2/ref/forms/fields/
+'''
+class VorlagenForm(forms.Form):
+    vorlagen = forms.ChoiceField(choices=())
+    def __init__(self, *args, templates=[('AB', 'ab'),('BC','bc')], **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields['vorlagen'].choices = templates
+
+class VorlagenVerwaltungForm(forms.Form):
+    vorlagen_zu_loschen = forms.MultipleChoiceField(
+        widget=forms.CheckboxSelectMultiple, 
+        choices=(),
+        label="Vorlagen zum Löschen vormerken:",
+        required=False
+    )
+    vorlage_neu = forms.FileField(label='Neue Vorlage installieren:', required=False)
+    def __init__(self, *args, vorlagen=[('AB', 'ab'),('BC','bc')], **kwargs):
+        self.vorlagen = vorlagen
+        super().__init__(*args, **kwargs)
+        self.fields['vorlagen_zu_loschen'].choices = vorlagen
+
+class DatenfileForm(forms.Form):
+    dateien = forms.ChoiceField(choices=())
+    def __init__(self, *args, dateien=[('should', 'not'),('be','here')], **kwargs):
+        super().__init__(*args, **kwargs)
+        self.fields['dateien'].choices = dateien
+
+class DatenfileVerwaltungForm(forms.Form):
+    dateien_zu_loschen = forms.MultipleChoiceField(
+        widget=forms.CheckboxSelectMultiple, 
+        choices=(),
+        label="Dateien zum Löschen vormerken:",
+        required=False
+    )
+    datei_verwendung = forms.ChoiceField(
+        choices=(),
+        label="Datei zur Verwendung:",
+        required=False
+    )
+    datei_neu = forms.FileField(label='Neue Datei verwenden:', required=False)
+    def __init__(self, *args, datenquellen=[('should', 'not'),('be','here')], **kwargs):
+        self.dateien = datenquellen
+        super().__init__(*args, **kwargs)
+        self.fields['dateien_zu_loschen'].choices = datenquellen
+        self.fields['datei_verwendung'].choices = [(None, "Auswählen ...")]+datenquellen
+
+class BeitragForm(forms.Form):
+    basisbeitrag  = forms.IntegerField()
+    zusatzbeitrag = forms.IntegerField(label="Beitrag für Zusatzgrundstück")
+
+class MitgliederForm(forms.Form):
+    mg_auswahl = forms.ChoiceField(
+            choices = (),
+            widget=forms.RadioSelect,
+            label = ""
+        )
+    def __init__(self, *args, zul="", **kwargs):
+        #self.templates = templates
+        super().__init__(*args, **kwargs)
+        self.zulstr = zul
+        self.fields['mg_auswahl'].choices = [("alle","Alle Mitglieder"), ("zul",f"Zuletzt ausgewählte Mitglieder: {zul}")]
+
+class MitgliederAuswahlForm(forms.Form):
+    mg_auswahl = forms.MultipleChoiceField(
+        widget=forms.CheckboxSelectMultiple, 
+        choices=(),
+        label=""
+    )
+    def __init__(self, *args, mitglieder=(), **kwargs):
+        #self.templates = templates
+        super().__init__(*args, **kwargs)
+        self.fields['mg_auswahl'].choices = mitglieder
+
+class KassenbriefForm_alt(forms.Form):
+    #briefpapier   = forms.FileField(label='Briefpapier ändern:', required=False)
+    template      = forms.ChoiceField(choices=())
+    basisbeitrag  = forms.IntegerField()
+    zusatzbeitrag = forms.IntegerField(label="Beitrag für Zusatzgrundstück")
+    def __init__(self, *args, templates=[('AB', 'ab'),('BC','bc')], **kwargs):
+        #self.templates = templates
+        super().__init__(*args, **kwargs)
+        self.fields['template'].choices = templates
+
+class EhmeldungForm(forms.Form):
+    VorZuname = forms.CharField(
+        label = "Vor- und Zuname",)
+    VorZunamePartner = forms.CharField(
+        label = "Vor- und Zuname Partner",
+        required=False)
+    Wohnanschrift = forms.CharField(label = "Wohnanschrift (Str, PLZ, Ort)")
+    Telefon = forms.CharField(
+        label = "Telefon",
+        required=False)
+    Email = forms.CharField(
+        label = "E-Mail",
+        required=False)
+    Geburtsdatum = forms.CharField(
+        label = "Geburtsdatum",
+        required=False)
+    VersichertesObjekt = forms.CharField(
+        label = "Versichertes Objekt",
+        required=False)
+    AnzahlWohnungen = forms.IntegerField(initial=1, label="Anzahl Wonungen")
+    Selbstgenutzt = forms.BooleanField(label="Selbstgenutzt", required=False)
+    Eigentumswohnung = forms.BooleanField(label="Eigentumswohnung", required=False)
+    Gewerblich = forms.BooleanField(label="(teilw.) gewerblich genutzt", required=False)
+    Eintrittsdatum = forms.CharField(
+        label = "Eintrittsdatum",
+        required=False)
+    
+class LoginForm(forms.Form):
+    verein   = forms.CharField(label = "Vereinskürzel")
+    name     = forms.CharField(label = "Login-Name")
+    password = forms.CharField(widget=forms.PasswordInput, label = "Passwort")
+    next     = forms.CharField(widget = forms.HiddenInput(), required = False)
diff --git a/python/eh_app/migrations/.dummy b/python/eh_app/migrations/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_app/migrations/0001_initial.py b/python/eh_app/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..84ca6ad
--- /dev/null
@@ -0,0 +1,23 @@
+# Generated by Django 2.2.28 on 2024-11-08 19:25
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ConfigData',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('verein', models.CharField(max_length=100)),
+                ('key', models.CharField(max_length=50)),
+                ('value', models.CharField(max_length=200)),
+            ],
+        ),
+    ]
diff --git a/python/eh_app/migrations/__init__.py b/python/eh_app/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_app/models.py b/python/eh_app/models.py
new file mode 100644 (file)
index 0000000..ff9d39a
--- /dev/null
@@ -0,0 +1,14 @@
+from django.db import models
+
+"""
+Falls hier Änderungen gemacht werden:
+- ./manage.py makemigrations eh_app
+- ./manage.py migrate
+"""
+
+class ConfigData(models.Model):
+    verein = models.CharField(max_length=100)
+    key    = models.CharField(max_length=50)
+    value  = models.CharField(max_length=200)
+
+    
\ No newline at end of file
diff --git a/python/eh_app/qmodels.py b/python/eh_app/qmodels.py
new file mode 100644 (file)
index 0000000..c58d933
--- /dev/null
@@ -0,0 +1,169 @@
+'''
+Created on 04.12.2024
+
+@author: sparky2021
+'''
+from q import QError
+from qif import QIF
+from .AWK import config
+import datetime
+from django.utils.decorators import classproperty
+import os
+
+def create_mitglied_from_dataset(data):
+    eintrittsdatum = datetime.datetime.strptime(data[8], '%d.%m.%Y')
+    if eintrittsdatum > datetime.datetime.today():
+        return None
+    if data[9] != "":
+        austrittsdatum = datetime.datetime.strptime(data[9], '%d.%m.%Y')
+        if austrittsdatum < datetime.datetime.today():
+            return None
+    m = Mitglied()
+    m.eintrittsdatum = eintrittsdatum
+    m.mitgliedsnr    = data[0]
+    m.anrede = data[1]
+    m.vorname = data[2]
+    m.nachname = data[3]
+    m.strasse  = data[4]
+    # "Zusatzadresse"
+    m.plz      = data[5]
+    m.ort      = data[6]
+    
+    # "Land"
+    # "Titel"
+    # "Geschlecht"
+    # "Familienstand"
+    # m.geburtsdatum   = data[7]
+    # "Austrittsgrund"
+    # m.zahlungsart    = data[16]
+    m.iban           = data[11]
+    # m.bic            = data[18]
+    # "Kontonummer"
+    # "Bankleitzahl"
+    # "Kreditinstitut"
+    # m.kontoinhaber   = data[22]
+    # m.mandatsreferenz = data[23]
+    # m.debitorenkontonr = data[24]
+    # m.status           = data[25]
+    # "Branche"
+    # "Notfallnummer"
+    # "Notfallkontakt"
+    # m.email            = data[29] # "KommE-Mail_P1"
+    # "KommFax_P1"
+    # "KommMobil_P1"
+    # "KommWeb_P1"
+    # "KommTelefon_P1"
+    # "KommSkype_P1"
+    # m.abteilung_1      = data[35]
+    # m.abteilungseintritt_1 = data[36]
+    # m.abteilungsaustritt_1 = data[37]
+    # if data[67] == '""':
+    #     m.stammnummer = 0
+    # else:
+    #     m.stammnummer       = int(m.rd_opt_qval(data[67]))
+    # "Freifeldname_2"
+    # m.ver_grund           = m.rd_opt_qval(data[69])
+    # if len(m.ver_grund) == 0:
+    #     m.ver_grund = f"{m.strasse}, {m.plz} {m.ort}"
+    # "Freifeldname_3"
+    # m.zeitung             = data[71]
+    return m
+    
+class MitgliederQuery:
+    def __init__(self):
+        self.data_source = QIF({"d" : ";", "H" : 1})
+        '''
+            d (separator)  : ;
+            H (header line): yes
+        '''
+        #cf = config.getInstance()
+        #self.file_name = os.path.join(cf.requireConfig("data_path"), "quellen", cf.requireConfig("datenquelle"))
+        self.file_name = "undefined. Set by .init(verein)"
+        self.att_list = "MitgliedschaftsNr, Mitgliedschaftsanrede, Vorname1, Nachname1, Strasse1, Plz1, Ort1, Geburtsdatum1, EintrittVerein, AustrittVerein, Zahlungsart, IBAN, BIC, Kontoinhaber, MandatsReferenz, Email"
+        #self.att_list = "Mitgliedschaftsanrede, Vorname1, Nachname1, Strasse1, Plz1, Ort1, Geburtsdatum1, EintrittVerein, AustrittVerein, Zahlungsart, IBAN, BIC, Kontoinhaber, MandatsReferenz, Email"
+        #                 0              1       2        3          4        5    6    7             8               9               10           11    12   13            14     15         16              17
+        cf = config.getInstance()
+        self.file_name = os.path.join(cf.requireConfig("data_path"), "quellen", cf.requireConfig("datenquelle"))
+        
+    def all(self):
+        data_set = self.data_source.request(f"SELECT {self.att_list} FROM {self.file_name} ORDER BY Nachname1, Vorname1")
+        if type(data_set) is QError:
+            raise RuntimeError(data_set)
+        mm = []
+        for data in data_set:
+            m = create_mitglied_from_dataset(data)
+            if m:
+                mm.append(m)
+        return mm
+    
+    def get(self, mitgliedsnr):
+        data_set = self.data_source.request(f"SELECT {self.att_list} FROM {self.file_name} WHERE MitgliedschaftsNr = {mitgliedsnr}")
+        if type(data_set) is QError:
+            raise RuntimeError(data_set)
+        if data_set[0]:
+            return create_mitglied_from_dataset(data_set[0])
+        return None
+    
+    def VersicherteGrundstucke(self, mitglied):
+        # Hole Basiseintrag
+
+        data_set = self.data_source.request(f"SELECT Objekt FROM {self.file_name} WHERE MitgliedschaftsNr = {mitglied.mitgliedsnr}")
+        if type(data_set) is QError:
+            raise RuntimeError(data_set)
+        if data_set[0][0]:
+            gr = data_set[0][0]
+        else:
+            gr = f"{mitglied.strasse}, {mitglied.plz} {mitglied.ort}"
+        gst = [gr]
+        
+        # Suche nach weiteren Grunstücken
+        data_set = self.data_source.request(f"SELECT `Weiteres Grundstück 1` FROM {self.file_name} WHERE MitgliedschaftsNr = {mitglied.mitgliedsnr}")
+        if type(data_set) is QError:
+            raise RuntimeError(data_set)
+        if data_set[0][0] != "":
+            gst.append(data_set[0][0])
+                
+        data_set = self.data_source.request(f"SELECT `Weiteres Grundstück 2` FROM {self.file_name} WHERE MitgliedschaftsNr = {mitglied.mitgliedsnr}")
+        if type(data_set) is QError:
+            raise RuntimeError(data_set)
+        if data_set[0][0] != "":
+            gst.append(data_set[0][0])
+                
+        data_set = self.data_source.request(f"SELECT `Weiteres Grundstück 3` FROM {self.file_name} WHERE MitgliedschaftsNr = {mitglied.mitgliedsnr}")
+        if type(data_set) is QError:
+            raise RuntimeError(data_set)
+        if data_set[0][0] != "":
+            gst.append(data_set[0][0])
+                
+        return gst
+class classproperty(object):
+    def __init__(self, f):
+        self.f = f
+    def __get__(self, obj, owner):
+        return self.f(owner)
+     
+class Mitglied:
+    #def objects = MitgliederQuery()
+    _objects = None
+    @classproperty
+    def objects(cls):
+        if not cls._objects:
+            cls._objects = MitgliederQuery()
+            ############################ Hack #########################
+            #cls._objects.init("svbal")
+        return cls._objects
+        
+    
+    #@property : nette Idee, funktioniert aber nicht auf Klassenebene
+    '''
+    def objects():
+        if not _objects:
+            _objects = MitgliederQuery()
+        print ("xxxxxxxxxxxxxxxxxxxxxxtype _objects",type(_objects))
+        return _objects
+    '''
+    def __init__(self):
+        pass
+    
+    def VersicherteGrundstucke(self):
+        return self.objects.VersicherteGrundstucke(self)
diff --git a/python/eh_app/templates/.dummy b/python/eh_app/templates/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_app/templates/__init__.py b/python/eh_app/templates/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_app/templates/datenquelle_andern.html b/python/eh_app/templates/datenquelle_andern.html
new file mode 100644 (file)
index 0000000..a022df5
--- /dev/null
@@ -0,0 +1,18 @@
+{% load static %}
+<html>
+<head>
+       <link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}">
+       <title>Siedlerverein-Verwaltung by WagnerTech UG</title>
+</head>
+<body>
+<h1>Datenquelle ändern</h1>
+<form action="/eh-app/{{verein}}/datenquelle-andern/" method="post" enctype="multipart/form-data">
+{% csrf_token %}
+<table>
+{{datenquelle_form}}
+</table>
+<input type="hidden" name="letzte_seite" value="{{letzte_seite}}"/>
+<p><input type="submit" value="Ändern"/>
+</form>
+</body>
+</html>
diff --git a/python/eh_app/templates/eh_app.html b/python/eh_app/templates/eh_app.html
new file mode 100644 (file)
index 0000000..c288eac
--- /dev/null
@@ -0,0 +1,18 @@
+{% load static %}
+<html>
+<head>
+       <link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}">
+</head>
+<body>
+<h1>Anwendung Eigenheimerverband</h1>
+<h2>Funktionen für Verein {{verein_name}}</h2>
+<p><a href="/eh-app/{{verein}}/kassenbrief">Kassenbrief erstellen</a></p>
+<p><a href="/eh-app/{{verein}}/ehmeldung">Meldung Eigenheimerverband</a></p>
+<p><a href="/eh-app/{{verein}}/vertragsliste">Liste der Mitgliedschaften</a></p>
+<h2>Verwendete Datenquelle:</h2>
+<p style="color:red">{{fehler_quelle}}</p>
+<p>Datenquelle: {{datenquelle}}</datenquelle>
+<p><a href = "/eh-app/{{verein}}/datenquelle-andern/">Datenquelle ändern</a></p>
+
+</body>
+</html>
diff --git a/python/eh_app/templates/ehmeldung.html b/python/eh_app/templates/ehmeldung.html
new file mode 100644 (file)
index 0000000..9d22bfa
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+{% load static %}
+<html>
+<head>
+       <link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}">
+    <meta charset="utf-8">
+    <title>WagnerTech - Eigenheimer - Meldung</title>
+</head>
+
+<body>
+
+    <!-- Upload form. Note enctype attribute! -->
+    <form action="/eh-app/{{verein}}/ehmeldung/" method="post" enctype="multipart/form-data">
+        {% csrf_token %}
+         <p>{{ form.VorZuname.label_tag }} {{ form.VorZuname }}</p>
+         <p>{{ form.VorZunamePartner.label_tag }} {{ form.VorZunamePartner }}</p>
+         <p>{{ form.Wohnanschrift.label_tag }} {{ form.Wohnanschrift }}</p>
+         <p>{{ form.Telefon.label_tag }} {{ form.Telefon }}</p>
+         <p>{{ form.Email.label_tag }} {{ form.Email }}</p>
+         <p>{{ form.Geburtsdatum.label_tag }} {{ form.Geburtsdatum }}</p>
+         <p>{{ form.VersichertesObjekt.label_tag }} {{ form.VersichertesObjekt }}</p>
+         <p>{{ form.AnzahlWohnungen.label_tag }} {{ form.AnzahlWohnungen }}</p>
+         <p>{{ form.Selbstgenutzt.label_tag }} {{ form.Selbstgenutzt }}</p>
+         <p>{{ form.Eigentumswohnung.label_tag }} {{ form.Eigentumswohnung }}</p>
+         <p>{{ form.Gewerblich.label_tag }} {{ form.Gewerblich }}</p>
+         <p>{{ form.Eintrittsdatum.label_tag }} {{ form.Eintrittsdatum }}</p>
+
+        <p><input type="submit" value="Meldung erstellen"/></p>
+    </form>
+</body>
+
+</html>
diff --git a/python/eh_app/templates/kassenbrief.html b/python/eh_app/templates/kassenbrief.html
new file mode 100644 (file)
index 0000000..2c64ca8
--- /dev/null
@@ -0,0 +1,45 @@
+{% load static %}
+<html>
+<head>
+       <title>Siedlerverein-Verwaltung by WagnerTech UG</title>
+       <link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}">
+</head>
+<body>
+<h1>Erstellung Kassenbrief</h1>
+<p>Überprüfen Sie folgende Eingabedaten:</p>
+<hr>
+
+<form action="/eh-app/{{verein}}/kassenbrief/" method="post" enctype="multipart/form-data">
+{% csrf_token %}
+
+<h2>Datenquelle</h2>
+<p style="color:red">{{fehler_quelle}}</p>
+<p>Datenquelle: {{datenquelle}}</datenquelle>
+<p><a href = "/eh-app/{{verein}}/datenquelle-andern/?letzte_seite=kassenbrief">Datenquelle ändern</a></p>
+<hr>
+
+<h2>Mitgliederauswahl</h2>
+<table>
+{{mausw_form}}
+</table>
+<a href = "/eh-app/{{verein}}/mitglieder_auswahlen/">Mitglieder auswählen</a>
+<hr>
+
+<h2>Kassenbriefvorlagen</h2>
+<table>
+{{vorlagen_form}}
+</table>
+<br>
+<a href = "/eh-app/{{verein}}/vorlagen_verwalten/">Kassenbriefvorlagen verwalten</a>
+<hr>
+
+<h2>Beiträge</h2>
+<table>
+{{beitrag_form}}
+</table>
+<hr>
+
+<p><input type="submit" value="Weiter ..."/>
+</form>
+</body>
+</html>
diff --git a/python/eh_app/templates/kassenbrief_erfolg.html b/python/eh_app/templates/kassenbrief_erfolg.html
new file mode 100644 (file)
index 0000000..56fa0fb
--- /dev/null
@@ -0,0 +1,16 @@
+{% load static %}
+<html>
+<head>
+       <link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}">
+</head>
+<body>
+<h1>Erstellung Kassenbrief</h1>
+<p>Die Kassenbriefe wurden erfolgreich erstellt und können jetzt heruntergeladen werden.</p>
+<p>Anzahl: {{anzahl}}</p>
+<p>Gesamtbetrag: {{gesamtbetrag}}.-</p>
+<form action="/eh-app/{{verein}}/kassenbrief/download" method="get">
+{% csrf_token %}
+<input type="submit" name="download" value="Herunterladen"/>
+</form>
+</body>
+</html>
diff --git a/python/eh_app/templates/kassenbrief_zusammenfassung.html b/python/eh_app/templates/kassenbrief_zusammenfassung.html
new file mode 100644 (file)
index 0000000..41de1ee
--- /dev/null
@@ -0,0 +1,23 @@
+{% load static %}
+<html>
+<head>
+       <title>Siedlerverein-Verwaltung by WagnerTech UG</title>
+       <link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}">
+</head>
+<body>
+<h1>Erstellung Kassenbrief</h1>
+<p>Überprüfen Sie folgende Eingabedaten:</p>
+<table>
+<tr><td>Datenquelle</td><td>{{datenquelle}}</td></tr>
+<tr><td>Vorlage</td><td>{{vorlage}}</td></tr>
+<tr><td>Basisbeitrag</td><td>{{basisbeitrag}}</td></tr>
+<tr><td>Zusatzbeitrag</td><td>{{zusatzbeitrag}}</td></tr>
+<tr><td>Mitglieder</td><td>{{miglieder}}</td></tr>
+</table>
+<form action="/eh-app/{{verein}}/kassenbrief/zusammenfassung" method="post">
+{% csrf_token %}
+<input type="submit" name="zuruck" value="Zurück"/>&emsp;
+<input type="submit" name="erstellen" value="Erstellen"/>
+</form>
+</body>
+</html>
diff --git a/python/eh_app/templates/login.html b/python/eh_app/templates/login.html
new file mode 100644 (file)
index 0000000..02b2036
--- /dev/null
@@ -0,0 +1,19 @@
+{% load static %}
+<html>
+<head>
+       <title>Siedlerverein-Verwaltung by WagnerTech UG</title>
+       <link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}">
+</head>
+<body>
+<h1>Siedlerverein-Verwaltung by WagnerTech UG</h1>
+<h2>Login</h2>
+<form action="/eh-app/login" method="post" enctype="multipart/form-data">
+{% csrf_token %}
+{{fehlertext}}
+<table>
+{{form}}
+</table>
+<p><input type="submit" value="Anmelden"/>
+</form>
+</body>
+</html>
diff --git a/python/eh_app/templates/mitglieder_auswahlen.html b/python/eh_app/templates/mitglieder_auswahlen.html
new file mode 100644 (file)
index 0000000..89825e3
--- /dev/null
@@ -0,0 +1,17 @@
+{% load static %}
+<html>
+<head>
+       <title>Siedlerverein-Verwaltung by WagnerTech UG</title>
+       <link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}">
+</head>
+<body>
+<h1>Mitglieder auswählen</h1>
+<form action="." method="post" enctype="multipart/form-data">
+{% csrf_token %}
+<table>
+{{mausw_form}}
+</table>
+<p><input type="submit" value="Auswählen"/>
+</form>
+</body>
+</html>
diff --git a/python/eh_app/templates/vorlagen_verwalten.html b/python/eh_app/templates/vorlagen_verwalten.html
new file mode 100644 (file)
index 0000000..a26164c
--- /dev/null
@@ -0,0 +1,17 @@
+{% load static %}
+<html>
+<head>
+       <link rel="stylesheet" type="text/css" href="{% static 'admin/css/base.css' %}">
+       <title>Siedlerverein-Verwaltung by WagnerTech UG</title>
+</head>
+<body>
+<h1>Vorlagen verwalten</h1>
+<form action="/eh-app/{{verein}}/vorlagen_verwalten/" method="post" enctype="multipart/form-data">
+{% csrf_token %}
+<table>
+{{vorlagen_form}}
+</table>
+<p><input type="submit" value="Ändern"/>
+</form>
+</body>
+</html>
diff --git a/python/eh_app/tests.py b/python/eh_app/tests.py
new file mode 100644 (file)
index 0000000..5f6affd
--- /dev/null
@@ -0,0 +1,36 @@
+from django.test import TestCase
+
+# Create your tests here.
+from .AWK import config
+
+class TestConfig(TestCase):
+    def test_setter(self):
+        cf = config.getInstance("svbal")
+        cf.setConfig("bla", "blub")
+        
+        val = cf.getConfig("bla")
+        assert (val == "blub")
+        
+        # change setting
+        cf.setConfig("bla", "bar")
+        val = cf.getConfig("bla")
+        assert (val == "bar")
+        
+'''
+class TestQModel(TestCase):
+    def setUp(self):
+        cf = config.getInstance("svbal")
+        cf.setConfig("DataFile", "../../Test/etc/TestExport.csv")
+
+    def test_allinstances(self):
+        config.getInstance("svbal")
+        from eh_app.qmodels import Mitglied
+        mm = Mitglied.objects.all()
+        assert (len(mm) == 4)
+        gst = mm[0].VersicherteGrundstucke()
+        assert (len(gst) == 1)
+        for m in mm:
+            if m.nachname == "Wagner":
+                gst = m.VersicherteGrundstucke()
+                assert (len(gst) == 3)
+'''
\ No newline at end of file
diff --git a/python/eh_app/urls.py b/python/eh_app/urls.py
new file mode 100644 (file)
index 0000000..ee38c82
--- /dev/null
@@ -0,0 +1,20 @@
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+    path('<slug:verein>/datenquelle-andern/', views.datenquelle_andern),
+    path('<slug:verein>/kassenbrief/', views.kassenbrief),
+    path('<slug:verein>/vorlagen_verwalten/', views.vorlagen_verwalten),
+    path('<slug:verein>/mitglieder_auswahlen/', views.mitglieder_auswahlen),
+    path('<slug:verein>/kassenbrief/zusammenfassung', views.kassenbrief_zusammenfassung),
+    path('<slug:verein>/kassenbrief/erfolg', views.kassenbrief_erfolg),
+    path('<slug:verein>/kassenbrief/download', views.kassenbrief_download),
+    path('<slug:verein>/ehmeldung/', views.ehmeldung),
+    path('<slug:verein>/vertragsliste/', views.vertragsliste),
+    path('login/', views.login),
+    path('login', views.login),
+    path('<slug:verein>/', views.index),
+    path('<slug:verein>', views.index),
+    path('', views.login),
+]
diff --git a/python/eh_app/views.py b/python/eh_app/views.py
new file mode 100644 (file)
index 0000000..600c7db
--- /dev/null
@@ -0,0 +1,407 @@
+import os,sys
+
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponse, HttpResponseRedirect, FileResponse
+from django.shortcuts import render
+from django.template import loader
+
+from .AWK import config, routines, util
+#import .forms 
+from .forms import (
+    EhmeldungForm, 
+    VorlagenForm, 
+    BeitragForm, 
+    VorlagenVerwaltungForm, 
+    MitgliederForm, 
+    MitgliederAuswahlForm, 
+    DatenfileVerwaltungForm )
+from eh_app.AWK.pdf_ersteller import PdfErsteller
+
+from mysite import settings
+import time
+
+# Create your views here.
+
+# change working dir due to relative paths in scripts
+os.chdir(settings.BASE_DIR)
+
+try:
+    home = os.environ["HOME"]
+    if os.path.exists(f"{home}/.eclipse/org.eclipse.platform_4.18.0_1473617060_linux_gtk_x86_64/plugins/org.python.pydev.core_8.3.0.202104101217/pysrc/"):
+        # its a eclipse development environment
+        sys.path.append(f"{home}/.eclipse/org.eclipse.platform_4.18.0_1473617060_linux_gtk_x86_64/plugins/org.python.pydev.core_8.3.0.202104101217/pysrc/")
+        import pydevd
+        pydevd.settrace()
+
+except:
+    pass
+
+LOGIN_URL = "/eh-app/login"
+
+def erstelle_datenquelle_liste(data_path):
+    dirlist = None
+    vorlagen_liste = []
+    if os.path.isdir(data_path):
+        dirlist = os.listdir(data_path) # returns list
+    if dirlist:
+        for t in dirlist:
+            vorlagen_liste.append((t,t))
+    return vorlagen_liste
+
+def erstelle_vorlagen_liste(verein):
+    # Erstelle Vorlagenliste
+    os.chdir(settings.BASE_DIR)
+    dirlist = os.listdir()
+    util.schreibe_log(str(dirlist))
+    dirlist = None
+    vorlagen_liste = []
+    if os.path.isdir("eh_app/templates/"+verein):
+        dirlist = os.listdir("eh_app/templates/"+verein) # returns list
+    if dirlist:
+        for t in dirlist:
+            tpl_name_wo_ext = os.path.splitext(t)[0]
+            vorlagen_liste.append((t,tpl_name_wo_ext))
+    return vorlagen_liste
+
+@login_required(login_url=LOGIN_URL)
+def vorlagen_verwalten(request, verein):
+    vorlagen_liste = erstelle_vorlagen_liste(verein)
+    
+    if request.method == 'POST':
+        # Form auswerten
+        form = VorlagenVerwaltungForm(request.POST, request.FILES, vorlagen=vorlagen_liste)
+        if form.is_valid():
+            zu_loschen = form.cleaned_data['vorlagen_zu_loschen']
+            zu_install = request.FILES.get('vorlage_neu', None)
+            routines.vorlagen_verwalten(verein, zu_loschen, zu_install)
+        else:
+            raise RuntimeError(f"Form not valid: {form.errors}")
+        # Verarbeitung in routines.py aufrufen
+
+        # Wenn alles gut gegangen, zurück zum Kassenbrief
+        return HttpResponseRedirect(f'/eh-app/{verein}/kassenbrief')
+    else:
+        vorlagen_form = VorlagenVerwaltungForm(vorlagen=vorlagen_liste)
+        
+    template = loader.get_template("vorlagen_verwalten.html")
+    return HttpResponse(template.render(
+        {
+            "verein" : verein, 
+            "vorlagen_form" : vorlagen_form }, 
+        request))
+
+@login_required(login_url=LOGIN_URL)
+def datenquelle_andern(request, verein):
+    cf = config.getInstance(verein)
+    data_path = cf.requireConfig("data_path")
+    ehappdir  = cf.getConfig("ehappdir", "/opt/eh_app")
+    vereinchecker = f"{ehappdir}/bin/ehapp-verein-check.sh"
+    if not os.path.exists(vereinchecker):
+        raise RuntimeError("ehapp-verein-check.sh nicht gefunden: "+vereinchecker)
+
+    rc = os.system(f'{vereinchecker} {data_path}')
+    if rc != 0:
+        raise RuntimeError(f"ehapp-verein-check.sh fehlgeschlagen: {rc}")
+    quellen_path = os.path.join(data_path, "quellen")
+    datenquellen_liste = erstelle_datenquelle_liste(quellen_path)
+    
+    if request.method == 'POST':
+        # Form auswerten
+        form = DatenfileVerwaltungForm(
+            request.POST, 
+            request.FILES, 
+            datenquellen=datenquellen_liste)
+        if form.is_valid():
+            zu_loschen   = form.cleaned_data['dateien_zu_loschen']
+            zu_verwenden = form.cleaned_data['datei_verwendung']
+            zu_install   = request.FILES.get('datei_neu', None)
+            routines.datenquelle_andern(verein, quellen_path, zu_loschen, zu_verwenden, zu_install)
+        else:
+            raise RuntimeError(f"Form not valid: {form.errors}")
+        # Verarbeitung in routines.py aufrufen
+
+        # Wenn alles gut gegangen, zurück zum Kassenbrief
+        letzte_seite = request.POST.get("letzte_seite")
+        return HttpResponseRedirect(f'/eh-app/{verein}/{letzte_seite}')
+    else:
+        form = DatenfileVerwaltungForm(datenquellen=datenquellen_liste)
+        
+    letzte_seite = request.GET.get("letzte_seite") or ""
+    template = loader.get_template("datenquelle_andern.html")
+    return HttpResponse(template.render(
+        {
+            "verein"           : verein, 
+            "datenquelle_form" : form,
+            "letzte_seite"     : letzte_seite }, 
+        request))
+
+@login_required(login_url=LOGIN_URL)
+def mitglieder_auswahlen(request, verein):
+    mitglieder = (request.COOKIES.get('mausw2') or "").split(",")
+    config.getInstance(verein) # wird in qmodel gebraucht
+    from eh_app.qmodels import Mitglied
+    mm = Mitglied.objects.all()
+    mitglieder_liste = []
+    for m in mm:
+        mitglieder_liste.append((m.mitgliedsnr, m.nachname+" "+m.vorname))
+
+    if request.method == 'POST':
+        # Form auswerten
+        form = MitgliederAuswahlForm(
+            request.POST, 
+            request.FILES, 
+            mitglieder=mitglieder_liste)
+        if form.is_valid():
+            mg_ausw  = form.cleaned_data['mg_auswahl']
+        else:
+            raise RuntimeError(f"Form not valid: {form.errors}")
+        
+        # Wenn alles gut gegangen, zurück zum Kassenbrief
+        response = HttpResponseRedirect(f'/eh-app/{verein}/kassenbrief')
+        mg_ausw_str = ",".join(mg_ausw)
+        response.set_cookie("mausw2", mg_ausw_str)
+        return response
+    else:
+        mausw_form = MitgliederAuswahlForm(mitglieder=mitglieder_liste, initial={"mg_auswahl":mitglieder})
+        
+    template = loader.get_template("mitglieder_auswahlen.html")
+    return HttpResponse(template.render(
+        {
+            "verein" : verein, 
+            "mausw_form" : mausw_form }, 
+        request))
+
+@login_required(login_url=LOGIN_URL)
+def kassenbrief(request, verein):
+    '''
+    GET:
+    Diese Methode liest aus der Config die Einstellungen und zeigt sie an.
+    
+    POST: 
+    Diese Methode lädt die Zusammenfassung
+    
+    ---------------------------------------------------------------
+    Templateauswahl
+    
+    Ausgewähltes Template: <Wert aus Config>
+    Link -> Neue Templates installieren
+    ---------------------------------------------------------------
+    '''
+    
+    mg_ausw = request.COOKIES.get('mausw1') or "leer"
+    mg_zul  = request.COOKIES.get('mausw2') or "leer"
+    alles_ok = True
+    fehlertext_quelle  = ""
+    fehlertext_vorlage = ""
+    fehlertext_beitrag = ""
+    vconf = config.getInstance(verein)
+    datenquelle    = vconf.getConfig("datenquelle")
+    beitrag_basis  = vconf.getConfig("beitrag_basis", 0)
+    beitrag_zusatz = vconf.getConfig("beitrag_zusatz", 0)
+    vorlagen_liste = erstelle_vorlagen_liste(verein)
+    vorlage        = vconf.getConfig("vorlage", "")
+    mausw_form = MitgliederForm(
+        zul=mg_zul,
+        initial={"mg_auswahl"  : mg_ausw,}
+                 )
+    if request.method == 'POST':
+        # Prüfe Datenquelle
+        if not datenquelle:
+            fehlertext_quelle = "Datenquelle auswählen"
+            alles_ok = False
+        
+        # Prüfe Vorlage
+        vorlagen_form = VorlagenForm(request.POST, request.FILES, templates=vorlagen_liste)
+        if not vorlagen_form.is_valid():
+            raise RuntimeError("VorlagenForm invalid: "+str(vorlagen_form.errors))
+        vorlage = vorlagen_form.cleaned_data["vorlagen"]
+        if not vorlage:
+            fehlertext_vorlage = "Bitte Kassenbriefvorlage auswählen"
+            alles_ok = False
+
+        # Prüfe Beiträge
+        beitrag_form = BeitragForm(request.POST, request.FILES)
+        if not beitrag_form.is_valid():
+            raise RuntimeError("BeitragForm invalid: Formular nochmal schicken")
+        beitrag_basis  = beitrag_form.cleaned_data["basisbeitrag"]
+        beitrag_zusatz = beitrag_form.cleaned_data["zusatzbeitrag"]
+        if beitrag_basis == 0 or beitrag_zusatz == 0:
+            fehlertext_beitrag = "Beiträge ausfüllen"
+            alles_ok = False
+
+        routines.aktualisiere_config(vconf, vorlage, beitrag_form.cleaned_data)
+        
+        # Speichere Migliederauswahl in Cookie
+        mausw_form = MitgliederForm(request.POST, request.FILES)
+        if not mausw_form.is_valid():
+            raise RuntimeError("MitgliederForm invalid: "+str(mausw_form.errors))
+        mausw = mausw_form.cleaned_data["mg_auswahl"]
+        
+        
+        if alles_ok:
+            response = HttpResponseRedirect('zusammenfassung')
+            response.set_cookie("mausw1", mausw)
+            return response
+
+    # GET request / POST mit Fehlern
+    vorlagen_form = VorlagenForm(
+        templates=vorlagen_liste,
+        initial={'vorlagen' : vorlage } )
+    beitrag_form = BeitragForm(
+        initial={
+            "basisbeitrag"  : beitrag_basis,
+            "zusatzbeitrag" : beitrag_zusatz,
+    })
+    return render(request, 'kassenbrief.html', {
+        "datenquelle"  : datenquelle or "Keine Datenquelle ausgewählt",
+        'vorlagen_form': vorlagen_form, 
+        'beitrag_form' : beitrag_form, 
+        'mausw_form'   : mausw_form, 
+        'verein'       : verein,
+        'fehler_quelle': fehlertext_quelle
+    })
+
+@login_required(login_url=LOGIN_URL)
+def kassenbrief_zusammenfassung(request, verein):
+    vconf = config.getInstance(verein)
+    datenquelle    = vconf.getConfig("datenquelle")
+    beitrag_basis  = vconf.getConfig("beitrag_basis", 0)
+    beitrag_zusatz = vconf.getConfig("beitrag_zusatz", 0)
+    vorlage        = vconf.getConfig("vorlage")
+    mitglieder     = request.COOKIES.get('mausw1')
+    if mitglieder == "zul":
+        mitglieder = request.COOKIES.get('mausw2')
+    if request.method == 'POST':
+        post = request.POST
+        if "erstellen" in post:
+            data_path  = vconf.getConfig("data_path", "/var/ehapp")
+            build_name = time.strftime("%Y%m%d%H%M%S", time.localtime())
+
+            anzahl, gesamtbetrag = routines.pdfs_erstellen(request, verein, mitglieder, vorlage, data_path, build_name)
+            response = HttpResponseRedirect(f'/eh-app/{verein}/kassenbrief/erfolg')
+            response.set_cookie("build_name", build_name)
+            response.set_cookie("anzahl", anzahl)
+            response.set_cookie("gesamtbetrag", gesamtbetrag)
+            return response
+        else:
+            return HttpResponseRedirect(f'/eh-app/{verein}/kassenbrief')
+        
+    else:
+        tpl_name_wo_ext = os.path.splitext(vorlage)[0]
+        return render(request, 'kassenbrief_zusammenfassung.html', {
+            'verein'        : verein,
+            'datenquelle'   : datenquelle,
+            'vorlage'       : tpl_name_wo_ext,
+            'basisbeitrag'  : beitrag_basis,
+            'zusatzbeitrag' : beitrag_zusatz,
+            'miglieder'     : mitglieder        })
+
+@login_required(login_url=LOGIN_URL)
+def kassenbrief_erfolg(request, verein):
+    return render(request, 'kassenbrief_erfolg.html', {
+            'verein'       : verein,
+            'anzahl'       : request.COOKIES.get('anzahl'),
+            'gesamtbetrag' : request.COOKIES.get('gesamtbetrag'),
+        })
+
+@login_required(login_url=LOGIN_URL)
+def kassenbrief_download(request, verein):
+    vconf = config.getInstance(verein)
+    data_path  = vconf.getConfig("data_path", "/var/ehapp")
+    build_name = request.COOKIES.get('build_name')
+    
+    ergebnis_datei = os.path.join(data_path, "build", build_name, "ergebnis.tgz")
+
+    response = FileResponse(open(ergebnis_datei, 'rb'), as_attachment=True, filename=f"{build_name}.tgz")
+    return response
+
+@login_required(login_url=LOGIN_URL)
+def ehmeldung(request, verein):
+    # if this is a POST request we need to process the form data
+    if request.method == 'POST':
+        # create a form instance and populate it with data from the request:
+        form = EhmeldungForm(request.POST, request.FILES)
+        # check whether it's valid:
+        if form.is_valid():
+            # bei Änderungen Konfiguration aktualisieren
+            routines.erstelle_ehmeldung(form.cleaned_data)
+            routines.erstelle_ausweis(form.cleaned_data)
+            # redirect to a new URL:
+            return HttpResponseRedirect('/thanks/')
+
+    # if a GET (or any other method) we'll create a blank form
+    else:
+        form = EhmeldungForm()
+
+    return render(request, 'ehmeldung.html', {'form': form, 'verein': verein})
+
+@login_required(login_url=LOGIN_URL)
+def vertragsliste(request, verein):
+    vconf = config.getInstance(verein)
+    data_path  = vconf.getConfig("data_path", "/var/ehapp")
+    ergebnis_datei = routines.vertragsliste_erstellen(verein, data_path)
+    response = FileResponse(open(os.path.join(data_path, "build",ergebnis_datei), 'rb'), as_attachment=True, filename=ergebnis_datei)
+    return response
+
+def index(request, verein):
+    if request.user.is_authenticated:
+        print("index login ok")
+    else:
+        print("index not logged in")
+
+    datenquelle = config.getInstance(verein).getConfig("datenquelle")
+
+    template = loader.get_template('eh_app.html')
+    response = HttpResponse(template.render(
+        {"verein"      : verein,
+         "verein_name" : verein,
+         "datenquelle" : datenquelle,
+        }, request))
+    return response    
+
+def login(request):
+    from django.contrib import auth
+    from .forms import LoginForm
+
+    fehlertext = ""
+    # if this is a POST request we need to process the form data
+    if request.method == 'POST':
+        # create a form instance and populate it with data from the request:
+        form = LoginForm(request.POST, request.FILES)
+        # check whether it's valid:
+        if form.is_valid():
+            user = auth.authenticate(
+                username = request.POST["name"], 
+                password = request.POST["password"],
+            )
+            verein = request.POST["verein"]
+        if user is not None:
+            
+            # check group
+            for group in user.groups.all():
+                if group.name == verein:
+                    auth.login(request, user)
+                    if request.user.is_authenticated:
+                        print("auth login ok")
+                    else:
+                        print("auth not logged in")
+                    next = request.POST.get("next", None)
+                    if next:
+                        return HttpResponseRedirect(next)
+                    else:
+                        return HttpResponseRedirect(f'/eh-app/{verein}')
+        # login fehlgeschlagen
+        fehlertext = "Login fehlgeschlagen"
+
+    # if a GET (or any other method) we'll create a blank form
+    else:
+        next   = None
+        verein = ""
+        next = request.GET.get("next", None)
+        if next:
+            path_elems = next.split("/")
+            verein = path_elems[2]
+        form = LoginForm(initial={'next': next, 'verein': verein})
+
+    return render(request, 'login.html', {'form': form, 'fehlertext': fehlertext})
+    
\ No newline at end of file
diff --git a/python/eh_util/.dummy b/python/eh_util/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/ausweis/.dummy b/python/eh_util/ausweis/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/ausweis/AWK/.dummy b/python/eh_util/ausweis/AWK/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/ausweis/AWK/config.py b/python/eh_util/ausweis/AWK/config.py
new file mode 100644 (file)
index 0000000..9011222
--- /dev/null
@@ -0,0 +1,37 @@
+'''
+Created on 02.08.2024
+
+@author: sparky2021
+'''
+from ausweis.models import ConfigData
+
+the_instance = None
+
+class Config:
+    '''
+    Singleton Klasse für Konfiguration
+    '''
+
+    def __init__(self, verein):
+        self.verein = verein
+        
+    def getConfig(self, key):
+        data = ConfigData.objects.filter(verein=self.verein, key=key)
+        if data:
+            return data[0].value
+        return None
+    
+    def requireConfig(self, key):
+        data = self.getConfig(key)
+        if not data:
+            raise RuntimeError(f"Kein Eintag für: {self.verein}/{key}")
+        return data
+        
+
+def getInstance(verein=None):
+    global the_instance
+    if not the_instance:
+        if not verein:
+            raise RuntimeError("Bei der ersten Instanzierung muss der Verein mitgegeben werden.")
+        the_instance = Config(verein)
+    return the_instance
diff --git a/python/eh_util/ausweis/AWK/routines.py b/python/eh_util/ausweis/AWK/routines.py
new file mode 100644 (file)
index 0000000..619048f
--- /dev/null
@@ -0,0 +1,12 @@
+from ausweis.AWK import config
+
+import os
+
+def handle_uploaded_file(csv_file):
+    csv_file_name = str(csv_file)
+    data_path = config.getInstance().requireConfig("data_path")
+    path = os.path.join(data_path, csv_file_name)
+    with open(path, 'wb+') as destination:
+        for chunk in csv_file.chunks():
+            destination.write(chunk)
+    
\ No newline at end of file
diff --git a/python/eh_util/ausweis/__init__.py b/python/eh_util/ausweis/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/ausweis/admin.py b/python/eh_util/ausweis/admin.py
new file mode 100644 (file)
index 0000000..6c6f054
--- /dev/null
@@ -0,0 +1,9 @@
+from django.contrib import admin
+
+# Register your models here.
+
+#from django.contrib import admin
+
+#from .models import ConfigData
+
+#admin.site.register(ConfigData)
diff --git a/python/eh_util/ausweis/apps.py b/python/eh_util/ausweis/apps.py
new file mode 100644 (file)
index 0000000..071f4d2
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class AusweisConfig(AppConfig):
+    name = 'ausweis'
diff --git a/python/eh_util/ausweis/forms.py b/python/eh_util/ausweis/forms.py
new file mode 100644 (file)
index 0000000..a16ef24
--- /dev/null
@@ -0,0 +1,17 @@
+from django import forms
+
+class UploadFileForm(forms.Form):
+    file1 = forms.FileField(label='S-Verein-Export')
+    
+class DocumentForm(forms.Form):
+    docfile = forms.FileField(
+        label='Select a file',
+        help_text='max. 42 megabytes'
+    )
+
+class TextInputForm(forms.Form):
+    text = forms.CharField(
+        label = "Text für das Anschreiben",
+        empty_value = "Bitte Text eingeben ...",
+        widget=forms.Textarea,
+    )
\ No newline at end of file
diff --git a/python/eh_util/ausweis/migrations/.dummy b/python/eh_util/ausweis/migrations/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/ausweis/migrations/__init__.py b/python/eh_util/ausweis/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/ausweis/models.py b/python/eh_util/ausweis/models.py
new file mode 100644 (file)
index 0000000..2c9d9a8
--- /dev/null
@@ -0,0 +1,8 @@
+from django.db import models
+
+"""
+Falls hier Änderungen gemacht werden:
+- ./manage.py makemigrations ausweis
+- ./manage.py migrate
+"""
+
diff --git a/python/eh_util/ausweis/templates/.dummy b/python/eh_util/ausweis/templates/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/ausweis/templates/__init__.py b/python/eh_util/ausweis/templates/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/ausweis/templates/anschreiben.html b/python/eh_util/ausweis/templates/anschreiben.html
new file mode 100644 (file)
index 0000000..42947b0
--- /dev/null
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>WagnerTech - Siedlerverein</title>
+</head>
+
+<body>
+<h1>Anschreiben für den Ausweis</h1>
+<form action="anschreiben" method="post" enctype="multipart/form-data">
+{% csrf_token %}
+<p>{{ form.text.label_tag }} {{ form.text }}</p>
+
+        <p><input type="submit" value="Anschreiben erstellen"/></p>
+</form>
+</body>
+</html>
diff --git a/python/eh_util/ausweis/templates/aus_index.html b/python/eh_util/ausweis/templates/aus_index.html
new file mode 100644 (file)
index 0000000..93369a6
--- /dev/null
@@ -0,0 +1,17 @@
+<html>
+<head>
+       <title>Siedlerverein-Verwaltung by WagnerTech UG</title>
+</head>
+<body>
+<h1>Erstellung Mitgliederausweise</h1>
+{% if csv_datei_name %}
+       <p>S-Verein-Export: {{csv_datei_name}}</p>
+       <p><a href="upload">Datei ändern</a></p>
+{% else %}
+       <p><a href="upload">Datei hochladen</a></p>
+{% endif %}
+<p><a href="anschreiben">Anschreiben erstellen</a></p>
+<p><a href="alle_ausweise">Alle Ausweise erstellen</a></p>
+<p><a href="einzelausweis">Einzelnen Ausweis erstellen</a></p>
+</body>
+</html>
diff --git a/python/eh_util/ausweis/templates/upload.html b/python/eh_util/ausweis/templates/upload.html
new file mode 100644 (file)
index 0000000..24ff0a2
--- /dev/null
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>WagnerTech - Siedlerverein</title>
+</head>
+
+<body>
+<h1>Upload der S-Verein-Exportdatei</h1>
+    <!-- Upload form. Note enctype attribute! -->
+    <form action="upload" method="post" enctype="multipart/form-data">
+        {% csrf_token %}
+         <p>{{ form.file1.label_tag }} {{ form.file1 }}</p>
+
+        <p><input type="submit" value="Datei hochladen"/></p>
+    </form>
+</body>
+
+</html>
diff --git a/python/eh_util/ausweis/tests.py b/python/eh_util/ausweis/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/python/eh_util/ausweis/urls.py b/python/eh_util/ausweis/urls.py
new file mode 100644 (file)
index 0000000..6567bea
--- /dev/null
@@ -0,0 +1,11 @@
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+    path('', views.index, name='index'),
+    path('upload', views.upload),
+    path('anschreiben', views.anschreiben),
+    path('alle_ausweise', views.alle_ausweise),
+    path('einzelausweis', views.einzelausweis),
+]
\ No newline at end of file
diff --git a/python/eh_util/ausweis/views.py b/python/eh_util/ausweis/views.py
new file mode 100644 (file)
index 0000000..c796800
--- /dev/null
@@ -0,0 +1,49 @@
+from .AWK import routines, config
+from .forms import UploadFileForm, DocumentForm, TextInputForm
+
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render
+from django.template import loader
+
+
+def index(request, verein):
+    
+    # check existence in DB
+    vconf = config.getInstance(verein)
+    csv_datei_name = vconf.getConfig("csv_datei_name")
+    if not csv_datei_name:
+        template = loader.get_template('aus_index.html')
+        context = {
+            'csv_datei_name': csv_datei_name,
+        }
+        return HttpResponse(template.render(context, request))
+    return render(request, 'index.html', {'csv_datei_name': csv_datei_name})
+
+def upload(request, verein):
+    if request.method == 'POST':
+        # initialize configuration
+        config.getInstance(verein)
+        form = UploadFileForm(request.POST, request.FILES)
+        if form.is_valid():
+            routines.handle_uploaded_file(request.FILES['file1'])
+            return HttpResponseRedirect(f'/{verein}/ausweis')
+        return HttpResponse("Dateiverarbeitung fehlerhaft")
+    else:
+        form = UploadFileForm()
+        return render(request, 'upload.html', {'form': form})
+
+def anschreiben(request, verein):
+    if request.method == 'POST':
+        form = TextInputForm(request.POST)
+        if form.is_valid():
+            return HttpResponse("Dateiverarbeitung ok")
+        return HttpResponse("Dateiverarbeitung fehlerhaft")
+    else:
+        form = TextInputForm()
+        return render(request, 'anschreiben.html', {'form': form})
+
+def alle_ausweise(request, verein):
+    return HttpResponse("NIY")
+
+def einzelausweis(request, verein):
+    return HttpResponse("NIY")
diff --git a/python/eh_util/eh_abgleich/.dummy b/python/eh_util/eh_abgleich/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_abgleich/AWK/.dummy b/python/eh_util/eh_abgleich/AWK/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_abgleich/AWK/SVereinControl.py b/python/eh_util/eh_abgleich/AWK/SVereinControl.py
new file mode 100644 (file)
index 0000000..606325d
--- /dev/null
@@ -0,0 +1,61 @@
+from AWK.mitglied import Mitglied
+class SVereinDatei(object):
+    '''
+    classdocs
+    '''
+
+    def __init__(self, datei_name):
+        '''
+        Constructor
+        '''
+        self.datei_name = datei_name
+        self.mitglieder = {}
+        self.get_merker = 0
+        
+        dh = open(datei_name)
+        line = dh.readline().rstrip()
+        export_header = line.split(";")
+        
+        sv_pn_spalte = -1
+        
+        i = 0
+        for kopf in export_header:
+            if kopf[0] == '"':
+                kopf = kopf[1:-1]
+            if kopf == 'Mitglieds-Nr':
+                sv_pn_spalte = i
+            i += 1
+        
+        if sv_pn_spalte == -1:
+            raise RuntimeError("S-Verein-Export enthält keine Partnernummer")
+
+        for line in dh:
+            data = line.rstrip().split(";")
+            pn = data[sv_pn_spalte]
+            if len(pn) == 0:
+                print("Diese Zeile (export) hat keine Migliedsnummer: ", line)
+            else:
+                if pn[0] == '"':
+                    pn = pn[1:-1]
+                pn = int(pn)
+                self.mitglieder[pn] = Mitglied(data)
+        # setze zusätzliche Mitgliedschaften im Stamm
+        for mnr in self.mitglieder:
+            if self.mitglieder[mnr].stammnummer > 0:
+                self.mitglieder[self.mitglieder[mnr].stammnummer].add_weitere_mitgliedschaft(mnr)
+
+    def get(self, mnr = None):
+        if mnr:
+            return self.mitglieder[mnr]
+        if self.get_merker == 0:
+            for m in self.mitglieder:
+                self.get_merker = m
+                return self.mitglieder[m]
+        return_next = False
+        for m in self.mitglieder:
+            if return_next:
+                self.get_merker = m
+                return self.mitglieder[m]
+            if m == self.get_merker:
+                return_next = True
+        return None
diff --git a/python/eh_util/eh_abgleich/AWK/__init__.py b/python/eh_util/eh_abgleich/AWK/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_abgleich/AWK/ausweis.py b/python/eh_util/eh_abgleich/AWK/ausweis.py
new file mode 100755 (executable)
index 0000000..79d33a1
--- /dev/null
@@ -0,0 +1,91 @@
+#!/usr/bin/python3
+
+import sys
+from mitglied import Mitglied
+
+"""
+Testaufruf:
+./ausweis.py Michael Wagner "25.5.1965" MitgliederausweisSVBaLVorlage_leer.pdf ./
+"""
+
+def erzeuge_ausweis(mitglied: Mitglied, ausgabetext, hintergrund_pdf, ausgabe_pfad):
+    from PyPDF2 import PdfFileWriter, PdfFileReader
+    from datetime import date
+    import io
+    from reportlab.pdfgen import canvas
+    from reportlab.lib.pagesizes import letter
+    
+    packet = io.BytesIO()
+    can = canvas.Canvas(packet, pagesize=letter)
+    
+    # Adressfeld
+    can.setFont("Helvetica", 12)
+    can.drawString(70, 640, mitglied.anrede)
+    can.drawString(70, 625, mitglied.vorname+" "+mitglied.nachname)
+    can.drawString(70, 610, mitglied.strasse)
+    can.drawString(70, 590, mitglied.plz+" "+mitglied.ort)
+
+    # Datum
+    can.setFont("Helvetica", 9)
+    can.drawString(350, 520, "München, "+date.today().strftime('%d.%m.%Y'))
+    
+    # Anschreiben
+    can.drawString(70, 500, "Sehr geehrte/r "+mitglied.anrede+" "+mitglied.nachname+",")
+    text = can.beginText(70, 480)
+    text.textLines(ausgabetext)
+    can.drawText(text)
+    
+    
+    can.line(20, 180, 560, 180)
+    
+    # Ausweis
+    can.setFont("Helvetica", 12)
+    can.drawString(460, 100, mitglied.mitgliedsnr)
+    can.drawString(330, 80, mitglied.vorname+" "+mitglied.nachname)
+    can.setFont("Helvetica", 9)
+    can.drawString(400, 56, mitglied.geburtsdatum)
+    can.drawString(400, 42, mitglied.eintrittsdatum)
+    can.drawString(400, 20, mitglied.ver_grund)
+    
+    
+    can.save()
+    
+    #move to the beginning of the StringIO buffer
+    packet.seek(0)
+    
+    # create a new PDF with Reportlab
+    new_pdf = PdfFileReader(packet)
+    # read your existing PDF
+    existing_pdf = PdfFileReader(open(hintergrund_pdf, "rb"))
+    output = PdfFileWriter()
+    # add the "watermark" (which is the new pdf) on the existing page
+    page = existing_pdf.pages[0]
+    page.mergePage(new_pdf.pages[0])
+    output.addPage(page)
+    # finally, write "output" to a real file
+    output_stream = open(ausgabe_pfad, "wb")
+    output.write(output_stream)
+    output_stream.close()
+
+if __name__ == "__main__":
+    if len(sys.argv) == 6:
+        nachname = sys.argv[1]
+        vorname = sys.argv[2]
+        geburtsdatum = sys.argv[3]
+        infile = sys.argv[4]
+        outpath = sys.argv[5]
+    else:
+        print ("Nachname: ")
+        nachname = input()
+        print ("Vorname:")
+        vorname = input()
+        print ("Geburtsdatum:")
+        geburtsdatum = input()
+        print ("Input-Datei: ")
+        infile = input()
+        print ("Ausgabepfad:")
+        outpath = input()
+        
+    mitglied = Mitglied(nachname, vorname, geburtsdatum)
+    erzeuge_ausweis(mitglied,infile, outpath)
+    
diff --git a/python/eh_util/eh_abgleich/AWK/mitglied.py b/python/eh_util/eh_abgleich/AWK/mitglied.py
new file mode 100644 (file)
index 0000000..a66b47a
--- /dev/null
@@ -0,0 +1,102 @@
+'''
+Created on 29.02.2024
+
+@author: sparky2021
+'''
+class Mitglied(object):
+    '''
+    classdocs
+    '''
+
+    def rd_opt_qval(self, s):
+        if s[0] == '"':
+            s = s[1:-1]
+        return s
+
+    def __init__(self, data):
+        '''
+        Constructor
+        '''
+        self.weitere_mitgliedschaften = []
+        self.anrede = data[0]
+        self.vorname = data[1]
+        self.nachname = data[2]
+        self.strasse  = self.rd_opt_qval(data[3])
+        # "Zusatzadresse"
+        self.plz      = data[5]
+        self.ort      = data[6]
+        # "Land"
+        # "Titel"
+        # "Geschlecht"
+        # "Familienstand"
+        self.mitgliedsnr    = self.rd_opt_qval(data[11])
+        self.geburtsdatum   = data[12]
+        self.eintrittsdatum = data[13]
+        self.austrittsdatum = data[14]
+        # "Austrittsgrund"
+        self.zahlungsart    = data[16]
+        self.iban           = data[17]
+        self.bic            = data[18]
+        # "Kontonummer"
+        # "Bankleitzahl"
+        # "Kreditinstitut"
+        self.kontoinhaber   = data[22]
+        self.mandatsreferenz = data[23]
+        self.debitorenkontonr = data[24]
+        self.status           = data[25]
+        # "Branche"
+        # "Notfallnummer"
+        # "Notfallkontakt"
+        self.email            = data[29] # "KommE-Mail_P1"
+        # "KommFax_P1"
+        # "KommMobil_P1"
+        # "KommWeb_P1"
+        # "KommTelefon_P1"
+        # "KommSkype_P1"
+        self.abteilung_1      = data[35]
+        self.abteilungseintritt_1 = data[36]
+        self.abteilungsaustritt_1 = data[37]
+        # "Abteilungsstatus_1"
+        # "Abteilungsstatus DFB_1"
+        # "Abteilungsaustrittsgrund_1"
+        # "Abteilung_2"
+        # "Abteilungseintritt_2"
+        # "Abteilungsaustritt_2"
+        # "Abteilungsstatus_2"
+        # "Abteilungsstatus DFB_2"
+        # "Abteilungsaustrittsgrund_2"
+        # "Beitragsbezeichnung_1_1"
+        # "Beitragsstart_1_1"
+        # "Beitragsende_1_1"
+        # "BeitragBerechnetBis_1_1"
+        # "BeitragZahlweise_1_1"
+        # "BeitragFälligkeitsdatum_1_1"
+        # "BeitragVariabel_1_1"
+        # "BeitragFormel_1_1"
+        # "BeitragGrundbetrag1_1_1"
+        # "BeitragGB1Gesperrt_1_1"
+        # "BeitragGrundbetrag2_1_1"
+        # "BeitragGB2Gesperrt_1_1"
+        # "BeitragGrundbetrag3_1_1"
+        # "BeitragGB3Gesperrt_1_1"
+        # "BeitragGrundbetrag4_1_1"
+        # "BeitragGB4Gesperrt_1_1"
+        # "BeitragGrundbetrag5_1_1"
+        # "BeitragGB5Gesperrt_1_1"
+        # "BeitragZahlweiseGesperrt_1_1"
+        # "Freifeldname_1"
+        if data[67] == '""':
+            self.stammnummer = 0
+        else:
+            self.stammnummer       = int(self.rd_opt_qval(data[67]))
+        # "Freifeldname_2"
+        self.ver_grund           = self.rd_opt_qval(data[69])
+        if len(self.ver_grund) == 0:
+            self.ver_grund = f"{self.strasse}, {self.plz} {self.ort}"
+        # "Freifeldname_3"
+        self.zeitung             = data[71]
+
+    def add_weitere_mitgliedschaft(self, pn):
+        self.weitere_mitgliedschaften.append(pn)
+        
+        
\ No newline at end of file
diff --git a/python/eh_util/eh_abgleich/MyTest.py b/python/eh_util/eh_abgleich/MyTest.py
new file mode 100755 (executable)
index 0000000..a2ffb6b
--- /dev/null
@@ -0,0 +1,52 @@
+#!/usr/bin/python3
+from AWK.SVereinControl import SVereinDatei
+from AWK import ausweis
+import sys
+
+def mytest(datei_name):
+    daten = SVereinDatei(datei_name)
+    print ("Daten geladen")
+    m = daten.get()
+    while m:
+        if m.abteilung_1 == "Standard" and len(m.austrittsdatum) == 0:
+            austxt = "als Mitglied unser Siedlervereinigung Berg am Laim wollen wir Sie\nüber Ihren Mitgliedsbeitrag für 2024 informieren.\n\n"
+            austxt += "Ihr Beitrag für 2024 setzt sich wie folgt zusammen:\n"
+            austxt += f"Standardmitgliedschaft ({m.ver_grund}): 39.-"
+            betrag = 39
+            weitere = m.weitere_mitgliedschaften
+            for weitere_mnr in weitere:
+                wm = daten.get(weitere_mnr)
+                austxt += f"\nZusatzgrundstück ({wm.ver_grund}): 32.-"
+                betrag += 32
+            if len(m.iban) > 0:
+                iban = m.iban[0:3]+"XXXXXXXXXXXXX"+m.iban[-4:]
+                austxt += f"\n\nDer Betrag von {betrag}.- wird von Ihrem Konto {iban} mit Gläubiger ID DE08 ZZZ0 0000 9547 90\nund Mandatsreferenz {m.mandatsreferenz} eingezogen."
+            else:
+                austxt += f"\n\nBitte überweisen Sie den offnen Betrag von {betrag}.- auf unser Konto DE69 7019 0000 0003 2143 62\nbei der Münchner Bank."
+            austxt += "\n\nUnd nun endlich ist es so weit, dass wir Ihnen hiermit auch Ihren Mitgliedsausweis\n"
+            austxt += "zukommen lassen können. Bitte schneiden Sie den unteren Teil ab und unterschreiben Sie diesen.\n"
+            austxt += "Weitere Infos zum Einkaufen und die Einladung für die Hauptversammlung finden Sie in der Anlage.\n"
+            austxt += "Wir freuen uns auf Ihr Kommen. "
+            austxt += "\n\nMit freundlichen Grüßen,\nMichael Wagner\n(Vorstand)"
+            ausweis.erzeuge_ausweis(m, austxt, "MitgliederausweisSVBaLVorlage_leer.pdf", f"Ausweis_{m.nachname}_{m.mitgliedsnr}.pdf")
+            #return
+        m = daten.get()
+    print ("Ausweise erstellt.")    
+    
+def test_q():
+    import qif
+    qif = qif.QIF({"d" : ";", "H" : 1})
+    data = qif.request("select Vorname,Nachname from /home/sparky2021/SVBaL/tmp/export.csv")
+    
+    print(data)
+    
+    
+if __name__ == '__main__':
+    numArgs = len(sys.argv)
+    if (numArgs != 2):
+        raise RuntimeError("usage: MyTest DATEI") 
+
+    #mytest(sys.argv[1])
+    
+    test_q()
+    
diff --git a/python/eh_util/eh_abgleich/__init__.py b/python/eh_util/eh_abgleich/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_abgleich/admin.py b/python/eh_util/eh_abgleich/admin.py
new file mode 100644 (file)
index 0000000..8c38f3f
--- /dev/null
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/python/eh_util/eh_abgleich/apps.py b/python/eh_util/eh_abgleich/apps.py
new file mode 100644 (file)
index 0000000..4e44d82
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class EhAbgleichConfig(AppConfig):
+    name = 'eh_abgleich'
diff --git a/python/eh_util/eh_abgleich/awk.py b/python/eh_util/eh_abgleich/awk.py
new file mode 100644 (file)
index 0000000..c91675a
--- /dev/null
@@ -0,0 +1,2 @@
+def vergleiche_sverein_eheimer(f_sverein, f_eheimer):
+    return f"Es werden die Dateien {f_sverein} und {f_eheimer} vergleichen."
\ No newline at end of file
diff --git a/python/eh_util/eh_abgleich/forms.py b/python/eh_util/eh_abgleich/forms.py
new file mode 100644 (file)
index 0000000..d6fb153
--- /dev/null
@@ -0,0 +1,11 @@
+from django import forms
+
+class UploadFileForm(forms.Form):
+    file1 = forms.FileField(label='S-Verein-Export')
+    file2 = forms.FileField(label='Eigemheimer-Datei')
+    
+class DocumentForm(forms.Form):
+    docfile = forms.FileField(
+        label='Select a file',
+        help_text='max. 42 megabytes'
+    )
\ No newline at end of file
diff --git a/python/eh_util/eh_abgleich/migrations/.dummy b/python/eh_util/eh_abgleich/migrations/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_abgleich/migrations/__init__.py b/python/eh_util/eh_abgleich/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_abgleich/models.py b/python/eh_util/eh_abgleich/models.py
new file mode 100644 (file)
index 0000000..71a8362
--- /dev/null
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/python/eh_util/eh_abgleich/templates/.dummy b/python/eh_util/eh_abgleich/templates/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_abgleich/templates/__init__.py b/python/eh_util/eh_abgleich/templates/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_abgleich/templates/index.html b/python/eh_util/eh_abgleich/templates/index.html
new file mode 100644 (file)
index 0000000..4db5a23
--- /dev/null
@@ -0,0 +1,14 @@
+<html>
+<body>
+<h1>Eigenheimer - Werkzeuge</h1>
+<p>Hier befinden sich Werkzeuge, um den Datenbestand eines
+   Mitgliedsvereins des Eigenheimerverbands Bayern zu bearbeiten</p>
+
+<h2>Datenabgleich S-Verein - Verband</h2>
+<p>Dieses Werkzeug vergleicht die Export-Datei von S-Verein mit
+   der Mitgliedermeldung des Verbands. Als primäres Vergleichsmerkmal
+   wird dabei die Partnernummer verwendet.</p>
+<p><a href="eh_abgleich">Zum Abgleich</a></p>
+</body>
+</html>
+
diff --git a/python/eh_util/eh_abgleich/templates/list.html b/python/eh_util/eh_abgleich/templates/list.html
new file mode 100644 (file)
index 0000000..00aadd2
--- /dev/null
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <title>Minimal Django File Upload Example</title>
+    </head>
+
+    <body>
+        <!-- List of uploaded documents -->
+        {% if documents %}
+            <ul>
+                {% for document in documents %}
+                    <li><a href="{{ document.docfile.url }}">{{ document.docfile.name }}</a></li>
+                {% endfor %}
+            </ul>
+        {% else %}
+            <p>No documents.</p>
+        {% endif %}
+
+        <!-- Upload form. Note enctype attribute! -->
+        <form action="{% url 'list' %}" method="post" enctype="multipart/form-data">
+            {% csrf_token %}
+            <p>{{ form.non_field_errors }}</p>
+
+            <p>{{ form.docfile.label_tag }} {{ form.docfile.help_text }}</p>
+
+            <p>
+                {{ form.docfile.errors }}
+                {{ form.docfile }}
+            </p>
+            
+           
+
+            <p><input type="submit" value="Upload"/></p>
+        </form>
+    </body>
+
+</html>
diff --git a/python/eh_util/eh_abgleich/templates/upload.html b/python/eh_util/eh_abgleich/templates/upload.html
new file mode 100644 (file)
index 0000000..29d1439
--- /dev/null
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <title>WagnerTech - Eigenheimer - Abgleich</title>
+    </head>
+
+    <body>
+       
+        <!-- Upload form. Note enctype attribute! -->
+        <form action="{% url 'eh_abgleich' %}" method="post" enctype="multipart/form-data">
+            {% csrf_token %}
+             <p>{{ form.file1.label_tag }} {{ form.file1 }}</p>
+             <p>{{ form.file2.label_tag }} {{ form.file2 }}</p>
+
+            <p><input type="submit" value="Vergleich starten"/></p>
+        </form>
+    </body>
+
+</html>
diff --git a/python/eh_util/eh_abgleich/tests.py b/python/eh_util/eh_abgleich/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/python/eh_util/eh_abgleich/urls.py b/python/eh_util/eh_abgleich/urls.py
new file mode 100644 (file)
index 0000000..85458c9
--- /dev/null
@@ -0,0 +1,9 @@
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+    path('', views.index, name='index'),
+    path('ss', views.upload_file, name='upload_file1'),
+    path('aa', views.list, name='list')
+]
diff --git a/python/eh_util/eh_abgleich/views.py b/python/eh_util/eh_abgleich/views.py
new file mode 100644 (file)
index 0000000..73bca4b
--- /dev/null
@@ -0,0 +1,44 @@
+from django.shortcuts import render
+from django.http import HttpResponse, HttpResponseRedirect
+from eh_abgleich.forms import UploadFileForm, DocumentForm
+
+# Create your views here.
+
+
+def index(request):
+    return HttpResponse("Hello, world. You're at the polls index.")
+
+
+# Imaginary function to handle an uploaded file.
+#from somewhere import handle_uploaded_file
+
+def upload_file(request):
+    if request.method == 'POST':
+        form = UploadFileForm(request.POST, request.FILES)
+        if form.is_valid():
+            #handle_uploaded_file(request.FILES['file'])
+            return HttpResponseRedirect('/success/url/')
+    else:
+        form = UploadFileForm()
+    return render(request, 'upload.html', {'form': form})
+
+def list(request):
+    # Handle file upload
+    if request.method == 'POST':
+        form = DocumentForm(request.POST, request.FILES)
+        if form.is_valid():
+            #newdoc = Document(docfile = request.FILES['docfile'])
+            #newdoc.save()
+
+            # Redirect to the document list after POST
+            return HttpResponseRedirect(reverse('list'))
+    else:
+        form = DocumentForm() # A empty, unbound form
+
+    # Load documents for the list page
+    #documents = Document.objects.all()
+    documents = {}
+
+
+    # Render list page with the documents and the form
+    return render(request, 'list.html', {'documents': documents, 'form': form})
diff --git a/python/eh_util/eh_util/.dummy b/python/eh_util/eh_util/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_util/__init__.py b/python/eh_util/eh_util/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_util/forms.py b/python/eh_util/eh_util/forms.py
new file mode 100644 (file)
index 0000000..f404bde
--- /dev/null
@@ -0,0 +1,7 @@
+from django import forms
+
+class LoginForm(forms.Form):
+    verein   = forms.CharField(label = "Vereinskürzel")
+    name     = forms.CharField(label = "Login-Name")
+    password = forms.CharField(widget=forms.PasswordInput, label = "Passwort")
+    next     = forms.CharField(widget = forms.HiddenInput(), required = False)
diff --git a/python/eh_util/eh_util/settings.py b/python/eh_util/eh_util/settings.py
new file mode 100644 (file)
index 0000000..d90a3e5
--- /dev/null
@@ -0,0 +1,124 @@
+"""
+Django settings for eh_util project.
+
+Generated by 'django-admin startproject' using Django 2.2.28.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/2.2/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+MEDIA_ROOT = BASE_DIR
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'x8xmlmq_%a3^rp7@1x0n%ugebrp!4@hc*5@=g%&6fo@5m3g!kw'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+LOGIN_URL = "/eh-util/login"
+
+# Application definition
+
+INSTALLED_APPS = [
+       'eh_app.apps.EhAppConfig',
+#      'ausweis.apps.AusweisConfig',
+#      'kassenbrief.apps.KassenbriefConfig',
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.sessions',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+]
+
+MIDDLEWARE = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'eh_util.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [os.path.join(BASE_DIR, "eh_util/templates"), os.path.join(BASE_DIR, "eh_utils/eh_app/templates/svbal")],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = 'eh_util.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    #{
+    #    'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    #},
+    #{
+    #    'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    #},
+    #{
+    #    'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    #},
+    #{
+    #    'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    #},
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/2.2/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/2.2/howto/static-files/
+
+STATIC_URL = '/static/'
diff --git a/python/eh_util/eh_util/templates/.dummy b/python/eh_util/eh_util/templates/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_util/templates/__init__.py b/python/eh_util/eh_util/templates/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/eh_util/templates/index.html b/python/eh_util/eh_util/templates/index.html
new file mode 100644 (file)
index 0000000..450d7eb
--- /dev/null
@@ -0,0 +1,10 @@
+<html>
+<head>
+       <title>Siedlerverein-Verwaltung by WagnerTech UG</title>
+</head>
+<body>
+<h1>Siedlerverein-Verwaltung by WagnerTech UG</h1>
+<p>Wählen Sie Ihren Verein:</p>
+<p><a href="svbal/">Siedlervereinigung Berg am Laim</a></p>
+</body>
+</html>
diff --git a/python/eh_util/eh_util/templates/vbasis.html b/python/eh_util/eh_util/templates/vbasis.html
new file mode 100644 (file)
index 0000000..fee8c1f
--- /dev/null
@@ -0,0 +1,11 @@
+<html>
+<head>
+       <title>Siedlerverein-Verwaltung by WagnerTech UG</title>
+</head>
+<body>
+<h1>Siedlerverein-Verwaltung by WagnerTech UG</h1>
+<p>Verfügbare Funktionen für {{verein}}:</p>
+<p><a href="ausweis">Ausweiserstellung</a></p>
+<p><a href="kassenbrief">Erstellung der Kassenbriefe</a></p>
+</body>
+</html>
diff --git a/python/eh_util/eh_util/urls.py b/python/eh_util/eh_util/urls.py
new file mode 100644 (file)
index 0000000..344817a
--- /dev/null
@@ -0,0 +1,30 @@
+"""eh_util URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/2.2/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import include, path
+import eh_util
+from . import views
+
+urlpatterns = [
+    path('', views.index),
+    path('login/', views.login),
+    path('admin/', admin.site.urls),
+    path('eh-abgleich/', include('eh_abgleich.urls')),
+#    path('<slug:verein>/', views.vbasis),
+#    path('<slug:verein>/ausweis/', include('ausweis.urls')),
+#    path('<slug:verein>/kassenbrief/', include('kassenbrief.urls')),
+    path('<slug:verein>/', include('eh_app.urls')),
+]
diff --git a/python/eh_util/eh_util/views.py b/python/eh_util/eh_util/views.py
new file mode 100644 (file)
index 0000000..0262e40
--- /dev/null
@@ -0,0 +1,53 @@
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render
+from django.template import loader
+
+
+def index(request):
+    template = loader.get_template('index.html')
+    return HttpResponse(template.render({}, request))    
+
+def login(request):
+    from django.contrib import auth
+    from .forms import LoginForm
+
+    fehlertext = ""
+    # if this is a POST request we need to process the form data
+    if request.method == 'POST':
+        # create a form instance and populate it with data from the request:
+        form = LoginForm(request.POST, request.FILES)
+        # check whether it's valid:
+        if form.is_valid():
+            user = auth.authenticate(
+                username = request.POST["name"], 
+                password = request.POST["password"],
+            )
+        if user is None:
+            # login fehlgeschlagen
+            fehlertext = "Login fehlgeschlagen"
+            
+        else:
+            auth.login(request, user)
+            next = request.POST.get("next", None)
+            if next:
+                return HttpResponseRedirect(next)
+            else:
+                return HttpResponseRedirect('/')
+
+    # if a GET (or any other method) we'll create a blank form
+    else:
+        next   = None
+        verein = ""
+        next = request.GET.get("next", None)
+        if next:
+            path_elems = next.split("/")
+            verein = path_elems[1]
+        form = LoginForm(initial={'next': next, 'verein': verein})
+
+    return render(request, 'login.html', {'form': form, 'fehlertext': fehlertext})
+    
+def vbasis(request, verein):
+    template = loader.get_template("vbasis.html")
+    raise RuntimeError("blub")
+    return HttpResponse(template.render({"verein" : verein}, request))
+
diff --git a/python/eh_util/eh_util/wsgi.py b/python/eh_util/eh_util/wsgi.py
new file mode 100644 (file)
index 0000000..f5cccf3
--- /dev/null
@@ -0,0 +1,16 @@
+"""
+WSGI config for eh_util project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eh_util.settings')
+
+application = get_wsgi_application()
diff --git a/python/eh_util/kassenbrief/.dummy b/python/eh_util/kassenbrief/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/kassenbrief/__init__.py b/python/eh_util/kassenbrief/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/kassenbrief/admin.py b/python/eh_util/kassenbrief/admin.py
new file mode 100644 (file)
index 0000000..8c38f3f
--- /dev/null
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/python/eh_util/kassenbrief/apps.py b/python/eh_util/kassenbrief/apps.py
new file mode 100644 (file)
index 0000000..32be9fe
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class KassenbriefConfig(AppConfig):
+    name = 'kassenbrief'
diff --git a/python/eh_util/kassenbrief/migrations/.dummy b/python/eh_util/kassenbrief/migrations/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/kassenbrief/migrations/__init__.py b/python/eh_util/kassenbrief/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/eh_util/kassenbrief/models.py b/python/eh_util/kassenbrief/models.py
new file mode 100644 (file)
index 0000000..71a8362
--- /dev/null
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/python/eh_util/kassenbrief/tests.py b/python/eh_util/kassenbrief/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/python/eh_util/kassenbrief/urls.py b/python/eh_util/kassenbrief/urls.py
new file mode 100644 (file)
index 0000000..cad389e
--- /dev/null
@@ -0,0 +1,7 @@
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+    path('', views.index),
+]
\ No newline at end of file
diff --git a/python/eh_util/kassenbrief/views.py b/python/eh_util/kassenbrief/views.py
new file mode 100644 (file)
index 0000000..fab82f9
--- /dev/null
@@ -0,0 +1,8 @@
+from django.http import HttpResponse
+from django.shortcuts import render
+
+# Create your views here.
+
+def index(request):
+    
+    return HttpResponse("NYI")
diff --git a/python/sv-merger/.dummy b/python/sv-merger/.dummy
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/python/sv-merger/sv-merger.py b/python/sv-merger/sv-merger.py
new file mode 100755 (executable)
index 0000000..aa48d57
--- /dev/null
@@ -0,0 +1,115 @@
+#!/usr/bin/python3
+'''
+Created on 10.01.2024
+
+@author: sparky2021
+'''
+
+import sys
+
+if __name__ == '__main__':
+    pass
+
+def rd_opt_qval(s):
+     if s[0] == '"':
+         s = s[1:-1]
+     return s
+
+numArgs = len(sys.argv)
+if (numArgs != 4):
+    raise RuntimeError("usage: sv-merger EXPORT DELTA FIELD") 
+
+
+dh_export = open(sys.argv[1])
+dh_delta  = open(sys.argv[2])
+dh_import = open("import.csv", "w")
+
+# Lese die Kopfzeile aus EXPORT
+line = dh_export.readline().rstrip()
+print(line, file=dh_import)
+export_header = line.split(";")
+
+sv_pn_spalte = -1
+sv_such_spalte = -1
+
+i = 0
+for kopf in export_header:
+    if kopf[0] == '"':
+        kopf = kopf[1:-1]
+    if kopf == 'Freifeldwert_1':
+        sv_pn_spalte = i
+    if kopf == sys.argv[3]:
+        sv_such_spalte = i
+    i += 1
+
+if sv_pn_spalte == -1:
+    raise RuntimeError("S-Verein-Export enthält keine Partnernummer")
+if sv_such_spalte == -1:
+    raise RuntimeError("S-Verein-Export enthält nicht das gewünschte Datenfeld "+sys.argv[3])
+
+line = dh_delta.readline().rstrip()
+delta_header = line.split(",")
+
+delta_pn_sp = -1
+delta_such_sp = -1
+
+i = 0
+for kopf in delta_header:
+    print (kopf)
+    if rd_opt_qval(kopf) == "Partnernummer":
+        delta_pn_sp = i
+    if kopf == sys.argv[3]:
+        delta_such_sp = i
+    i += 1
+
+if delta_pn_sp == -1:
+    raise RuntimeError("Delta-Datei enthält keine Partnernummer")
+if delta_such_sp == -1:
+    raise RuntimeError("Delta-Datei enthält nicht das gewünschte Datenfeld")
+
+delta = {}
+for line in dh_delta:
+    if len(line.rstrip()):
+        # not a empty line
+        data = line.split(";")
+        if len(data[delta_pn_sp]) == 0:
+            print("Diese Zeile hat keine Migliedsnummer: ", line)
+        else:
+            delta[int(data[delta_pn_sp])] = data[delta_such_sp]
+
+for line in dh_export:
+    data = line.rstrip().split(";")
+    pn = data[sv_pn_spalte]
+    if len(pn) == 0:
+        print("Diese Zeile (export) hat keine Migliedsnummer: ", line)
+    else:
+        if pn[0] == '"':
+            pn = pn[1:-1]
+        pn = int(pn)
+        if pn in delta:
+            if len(delta[pn]):
+                if data[sv_such_spalte] != delta[pn]:
+                    data[sv_such_spalte] = delta[pn]
+                    print(";".join(data), file=dh_import)
+        else:
+            print(f"{pn} fehlt in delta-Datei")
+
+dh_export.close()
+dh_delta.close()
+dh_import.close()
+
+'''
+Argumente:
+
+EXPORT: Export-Datei aus S-Verein
+
+DELTA: csv-Daten, die eingearbeitet werden sollen
+
+FIELD: Feld, das eingearbeitet werden soll, wenn
+ - Das Quellfeld nicht leer ist
+ - Sich der Wert von der EXPORT-Datei unterscheidet
+
+Die Synchronisation der Daten erfolgt anhand der Partnernummer.
+
+Das Ergebnis wird stets in die Datei import.csv geschrieben.
+'''