server documents booking and drop bs-tbl
authorKilian Saffran <ksaffran@dks.lu>
Tue, 16 Apr 2019 17:55:03 +0000 (19:55 +0200)
committerKilian Saffran <ksaffran@dks.lu>
Tue, 16 Apr 2019 17:55:03 +0000 (19:55 +0200)
34 files changed:
css/app.css
db.invoicejournal.json [new file with mode: 0644]
index.html
main.js
modules/accounts/form_account.html [new file with mode: 0644]
modules/accounts/form_account.js [new file with mode: 0644]
modules/accounts/index.html
modules/accounts/index.js
modules/bookings/form_booking.html [new file with mode: 0644]
modules/bookings/form_booking.js [new file with mode: 0644]
modules/bookings/index.html [new file with mode: 0644]
modules/bookings/index.js [new file with mode: 0644]
modules/bookings/lib/booking.js [new file with mode: 0644]
modules/documents/index.html [new file with mode: 0644]
modules/documents/index.js [new file with mode: 0644]
package-lock.json
package.json
renderer.js
server/Module/FileSystem.pm [new file with mode: 0644]
server/Module/OpenVPN.pm [new file with mode: 0644]
server/Module/PDFExtract.pm [new file with mode: 0644]
server/Module/PDFExtract_checkservice.pm [new file with mode: 0644]
server/Module/SQLite.pm [new file with mode: 0644]
server/Module/Service.pm [new file with mode: 0644]
server/Module/Test.pm [new file with mode: 0644]
server/Module/VPNServer.pm [new file with mode: 0644]
server/createpdfA4invoice.pl [new file with mode: 0644]
server/fmtosqlite.pl [new file with mode: 0644]
server/invoicejournalserver.pl [new file with mode: 0644]
server/pdfextract.pl [new file with mode: 0644]
server/pdftextblock.pl [new file with mode: 0644]
server/pdftotext.exe [new file with mode: 0644]
server/sqlitedumpdata.pl [new file with mode: 0644]
server/syncfolder.pl [new file with mode: 0644]

index 207a847..f137f86 100644 (file)
@@ -60,7 +60,7 @@ select.bg-primary, select.bg-primary:hover, select.bg-primary:active  {
 }
 
 
-input.right {
+.right {
   text-align: right;
 }
 .table-hover tbody tr:hover {
@@ -72,4 +72,25 @@ input.right {
 
 .card {
   margin: 10px;
+}
+
+.thead-dark tr th select.form-control {
+  background-color: #212529;
+  background-image: url("data:image/svg+xml;utf8,<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 24 24'><path fill='#fff' d='M7.406 7.828l4.594 4.594 4.594-4.594 1.406 1.406-6 6-6-6z'></path></svg>");
+  color: #fff;
+  margin: 0px;
+}
+
+.table tbody tr.highlight td {
+  background-color: lightblue;
+}
+
+.noselect {
+  -webkit-touch-callout: none; /* iOS Safari */
+    -webkit-user-select: none; /* Safari */
+     -khtml-user-select: none; /* Konqueror HTML */
+       -moz-user-select: none; /* Firefox */
+        -ms-user-select: none; /* Internet Explorer/Edge */
+            user-select: none; /* Non-prefixed version, currently
+                                  supported by Chrome and Opera */
 }
\ No newline at end of file
diff --git a/db.invoicejournal.json b/db.invoicejournal.json
new file mode 100644 (file)
index 0000000..d507921
--- /dev/null
@@ -0,0 +1 @@
+{"name":"invoicejournal","type":"local","server":"dks-laptop","dbfile":"invoicejournal","vpn":"","vat":"0,17","currency":"€","mailserver":"","mailencryption":"","mailport":"","maillogin":"","mailpassword":""}
\ No newline at end of file
index 9382c6a..6928c2b 100644 (file)
                     <select class="form-control list-group-item list-group-item-action bg-primary text-white" id="globaldatasets" onchange="browserapp.loaddataset();">
                     </select>
                     <a  class="list-group-item list-group-item-action bg-light" href="javascript:browserapp.loadmodule('overview');">Übersicht</a>
+                    <a  class="list-group-item list-group-item-action bg-light" href="javascript:browserapp.loadmodule('bookings');">Buchungen</a>
                     <a  class="list-group-item list-group-item-action bg-light" href="javascript:browserapp.loadmodule('invoices');">Rechnungen</a>
                     <a class="list-group-item list-group-item-action bg-light" href="javascript:browserapp.loadmodule('accounts');">Konten</a>
                     <a class="list-group-item list-group-item-action bg-light" href="javascript:browserapp.loadmodule('bankaccount');">Bankkonto</a>
+                    <a class="list-group-item list-group-item-action bg-light" href="javascript:browserapp.loadmodule('documents');">Dokumente</a>
                     <a class="list-group-item list-group-item-action bg-light" href="javascript:browserapp.loadmodule('offers');">Angebote</a>
                     <a class="list-group-item list-group-item-action bg-light" href="javascript:browserapp.loadmodule('products');">Produkte</a>
                     <a class="list-group-item list-group-item-action bg-light" href="javascript:browserapp.loadmodule('templates');">Vorlagen</a>
diff --git a/main.js b/main.js
index 40e1e4e..97e77c0 100644 (file)
--- a/main.js
+++ b/main.js
@@ -12,10 +12,11 @@ let mainWindow
 function createWindow () {
   // Create the browser window.
   var executablePath = "";
-       var parameters = [];
+  var parameters = [];
+  console.log(app.getAppPath());
         if (os.platform() == "win32"){
                executablePath = "C:\\Strawberry\\perl\\bin\\perl.exe";
-               parameters = ["C:\\Users\\ksaff\\Workspace\\dks_server\\dkslocalserver.pl"];
+               parameters = ["C:\\Users\\ksaff\\Workspace\\Apps\\invoicejournal\\\\server\\invoicejournalserver.pl"];
         } else { //os.platform() == "darwin"
                executablePath = "/Users/kilian/perl5/perlbrew/perls/perl-5.28.1/bin/perl";
                parameters = ["/Users/kilian/Workspace/dks_server/dkslocalserver.pl"];
diff --git a/modules/accounts/form_account.html b/modules/accounts/form_account.html
new file mode 100644 (file)
index 0000000..05db2d9
--- /dev/null
@@ -0,0 +1,250 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta content="width=device-width, initial-scale=1.0" name="viewport">
+  <link href="../../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
+  <!-- <link href="../../node_modules/bootstrap-table/dist/bootstrap-table.min.css" rel="stylesheet"> -->
+  <!-- <link href="../../node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css" rel="stylesheet"> -->
+  <link href="../../node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css" rel="stylesheet">
+  <link href="../../css/invoicejournal.epic.css" rel="stylesheet">
+  <link href="../../css/app.css" rel="stylesheet">
+  <title>Konto</title>
+</head>
+<body>
+  <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
+    <button class="btn btn-primary" onclick="parent.browserapp.loadmodule('accounts');"><i class="fas fa-chevron-left"></i><br>Back</button> 
+    <a class="navbar-brand" href="#">Konto</a>
+    <div class="ml-auto">
+      <div aria-label="Basic example" class="btn-group" role="group">
+        
+        
+      </div>
+    </div>
+  </nav>
+  <div class="cotainer-fluid" style="margin-top: 52px;">
+    <!-- <ul class="nav nav-tabs" id="pagetab" role="tablist">
+      <li class="nav-item"><a aria-controls="invoice" aria-selected="true" class="nav-link active" data-toggle="tab" href="#invoice" id="invoice-tab" role="tab">Rechnung</a></li>
+      <li class="nav-item"><a aria-controls="files" aria-selected="false" class="nav-link" data-toggle="tab" href="#files" id="files-tab" role="tab">Dateien & Bezahlung</a></li>
+      <li class="nav-item"><a aria-controls="notes" aria-selected="false" class="nav-link" data-toggle="tab" href="#notes" id="notes-tab" role="tab">Zusatz-Texte</a></li>
+      
+    </ul>
+    <div class="tab-content" id="tabpagecontent">
+ <div aria-labelledby="invoice-tab" class="tab-pane fade show active" id="invoice" role="tabpanel">
+        <div class="row">
+          <div class="col-md-4">
+            <div class="col-md-12">
+                <div class="form-group row">
+                  <label for="id_sender" class="col-sm-2 col-form-label">Sender</label>
+                  <div class="col-sm-10"> 
+                    <select class="form-control" id="id_sender" name="id_sender"></select>
+                  </div>  
+                </div>
+                <div class="form-group row">
+                  <label for="id_receipient"  class="col-sm-2 col-form-label">Receiver</label> 
+                  <div class="col-sm-10">
+                    <select class="form-control" id="id_receipient" name="id_receipient"></select>
+                  </div>
+                </div>
+                <div class="form-group row">
+                  <label for="payedamount" class="col-sm-2 col-form-label" >Bezahlt</label> 
+                  <div class="col-sm-6 input-group">
+                    <input type="text" class="form-control" id="payedamount" name="payedamount" value="" />
+                    <div class="input-group-append">
+                        <span class="input-group-text">&euro;</span>
+                      </div>
+                  </div>
+                  
+                </div>
+            </div>
+          </div>
+          <div class="col-md-8">
+          <div class="row">
+          <div class="col-md-6">
+            <div class="col-md-12">
+              <div class="form-group row">
+                <label for="date" class="col-sm-2 col-form-label" >Datum</label>
+                <div class="col-sm-4">
+                  <input class="form-control datepicker" id="date" name="date" type="text" >
+                </div>
+                <label for="deadlinedate" class="col-sm-2 col-form-label" >Fälligkeit</label>
+                <div class="col-sm-4">
+                  <input class="form-control datepicker" id="deadlinedate" name="deadlinedate" type="text">
+                </div>
+              </div>
+              <div class="form-group row">
+                <label for="reference" class="col-sm-2 col-form-label" >Referenz</label>
+                <div class="col-sm-10">
+                  <input class="form-control" id="reference" name="reference" type="text">
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="col-md-6">
+              <div class="col-md-12">
+                <div class="form-group row">
+                  <label for="type" class="col-sm-2 col-form-label">Typ</label>
+                  <div class="col-sm-10"> 
+                    <select class="form-control" id="type" name="type"></select>
+                  </div>  
+                </div>
+                <div class="form-group row">
+                  <label for="id_template" class="col-sm-2 col-form-label">Template</label>
+                  <div class="col-sm-10"> 
+                    <select class="form-control" id="id_template" name="id_template"></select>
+                  </div>  
+                </div>
+              </div>
+          </div>
+          </div>
+          <div class="col-md-12">
+              <div class="row">
+              <div class="col-md-6">
+                  <div class="form-group row">
+                      <label for="netamount" class="col-sm-2 col-form-label" >Netto</label> 
+                      <div class="col-sm-4 input-group">
+                        <input type="text" class="form-control" id="netamount" name="netamount" value="" readonly/>
+                        <div class="input-group-append">
+                          <span class="input-group-text">&euro;</span>
+                        </div>
+                      </div>
+                      <label for="discountamount" class="col-sm-2 col-form-label">Rabatt</label> 
+                      <div class="col-sm-4 input-group">
+                        <input type="text" class="form-control"  id="discountamount" name="discountamount" value="" readonly/>
+                        <div class="input-group-append">
+                          <span class="input-group-text">&euro;</span>
+                        </div>
+                      </div>
+                    </div>
+              </div>
+              <div class="col-md-6">
+                  <div class="form-group row">
+                      <label for="vatamount" class="col-sm-2 col-form-label" >MwSt</label> 
+                      <div class="col-sm-4 input-group">
+                        <input type="text" class="form-control" id="vatamount" name="vatamount" value="" readonly/>
+                        <div class="input-group-append">
+                          <span class="input-group-text">&euro;</span>
+                        </div>
+                      </div>
+                      <label for="grossamount" class="col-sm-2 col-form-label">Brutto</label> 
+                      <div class="col-sm-4 input-group">
+                        <input type="text" class="form-control"  id="grossamount" name="grossamount" value="" readonly/>
+                        <div class="input-group-append">
+                            <span class="input-group-text">&euro;</span>
+                        </div>
+                      </div>
+                    </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        </div>
+        <div class="row">
+          <div class="col-md-12">
+            <nav class="navbar navbar-expand-md navbar-dark bg-dark">
+              <a class="navbar-brand" href="#">Positionen</a>
+              
+              <div class="ml-auto">
+                <div aria-label="Basic example" class="btn-group" role="group">
+                  <button class="btn btn-primary" onclick="position.new();"><i class="fas fa-plus"></i><br> New</button> 
+                  <button class="btn btn-primary" onclick="position.delete();"><i class="fas fa-trash"></i><br> Delete</button> 
+                  <button class="btn btn-primary" onclick="position.clone();"><i class="fas fa-copy"></i><br> Dupl.</button>
+                  <div class="btn-group">
+                    <button class="btn btn-primary" id="status" type="button"><i class="fas fa-box"></i><br> add Product</button> 
+                    <button aria-expanded="false" aria-haspopup="true" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" type="button"><span class="sr-only">Toggle Dropdown</span></button>
+                    <div class="dropdown-menu" id="productdata"></div>
+                  </div>
+                  <div class="btn-group">
+                    <button class="btn btn-primary" id="status" type="button"><i class="fas fa-business-time"></i><br> add Service</button> 
+                    <button aria-expanded="false" aria-haspopup="true" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" type="button"><span class="sr-only">Toggle Dropdown</span></button>
+                    <div class="dropdown-menu" id="servicedata"></div>
+                  </div>
+                </div>
+              </div>
+            </nav>
+          </div>
+        </div>
+        <div class="row">
+          <table class="table table-bordered table-hover table-striped" id="tbl_invoicedata">
+            <thead class="thead-dark">
+              <tr>
+                <th data-checkbox="true"></th>
+                <th data-sortable="true">Description</th>
+                <th data-sortable="true">Qty</th>
+                <th data-sortable="true">Unit</th>
+                <th data-sortable="true">Price</th>
+                <th data-sortable="true">VAT</th>
+                <th data-sortable="true">Discount</th>
+                <th data-sortable="true">Sums</th>
+              </tr>
+            </thead>
+          </table>
+        </div>
+      </div>
+      <div aria-labelledby="files-tab" class="tab-pane fade" id="files" role="tabpanel">
+        <div class="row">
+            <div class="col-md-8">
+                <table class="table table-bordered table-hover table-striped" id="tbl_payements">
+                    <thead class="thead-dark">
+                      <tr>
+                        <th data-checkbox="true"></th>
+                        <th data-sortable="true">Datum</th>
+                        <th data-sortable="true">Typ</th>
+                        <th data-sortable="true">Sender</th>
+                        <th data-sortable="true">Empfänger</th>
+                        <th data-sortable="true">KontoAuszug</th>
+                        <th data-sortable="true">Betrag</th>
+                      </tr>
+                    </thead>
+                  </table>
+            </div>
+          <div class="col-md-4">
+            <table class="table table-bordered table-hover table-striped" id="tbl_files">
+              <thead class="thead-dark">
+                <tr>
+                  <th data-checkbox="true"></th>
+                  <th data-sortable="true">Name</th>
+                  <th data-sortable="true">Type</th>
+                  <th data-sortable="true">Date</th>
+                </tr>
+              </thead>
+            </table>
+          </div>
+          
+        </div>
+      </div>
+      <div aria-labelledby="notes-tab" class="tab-pane fade" id="notes" role="tabpanel">
+        <div class="row">
+          <div class="col-md-12">
+            <div class="form-group">
+              <label for="preface">Vorwort</label>
+              <textarea class="form-control richtextedit" style="height: 200px;" id="preface" name="preface"></textarea>
+            </div>
+          </div>
+        </div>
+        <div class="row">
+          <div class="col-md-12">
+            <div class="form-group">
+              <label for="footnote">Nachwort</label>
+              <textarea class="form-control richtextedit" style="height: 200px;" id="footnote" name="footnote"></textarea>
+            </div>
+          </div>
+        </div>
+      </div>
+      
+    </div> -->
+  </div>
+  <script> if (typeof module === 'object') {window.module = module; module = undefined;}</script> 
+  <script src="../../node_modules/jquery/dist/jquery.min.js"></script> 
+  <script src="../../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script> 
+  <!-- <script src="../../node_modules/bootstrap-table/dist/bootstrap-table.min.js"></script> -->
+  <!-- <script src="../../node_modules/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"></script>   -->
+  <script src="../../node_modules/@fortawesome/fontawesome-free/js/all.min.js"></script> 
+  <script src="../../node_modules/tinymce/tinymce.min.js"></script> 
+  <script src="../../js/moduleglobal.js"></script> 
+  <script src="../../js/database.js"></script> 
+  <!-- <script src="lib/invoice.js"></script>  -->
+  <script src="form_account.js"></script> 
+  <script>if (window.module) module = window.module;</script>
+</body>
+</html>
diff --git a/modules/accounts/form_account.js b/modules/accounts/form_account.js
new file mode 100644 (file)
index 0000000..e69de29
index 2897c37..fbe2f07 100644 (file)
@@ -4,7 +4,6 @@
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <link rel="stylesheet" href="../../node_modules/bootstrap/dist/css/bootstrap.min.css">
-<link rel="stylesheet" href="../../node_modules/bootstrap-table/dist/bootstrap-table.min.css">
 <link rel="stylesheet" href="../../node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css">
 <link rel="stylesheet" href="../../css/invoicejournal.epic.css">
 <link rel="stylesheet" href="../../css/app.css">
    </div>
                 </div>
               </nav>
-    <div class="cotainer-fluid" style="margin-top: 52px;">            
+    <div class="cotainer-fluid" style="margin-top: 52px;">
+        <table style="width: 100%;" class="noselect">
+            <tr>
+              <td style="padding: 0px; margin: 0px;">
+                <table class="table" style="width: 100%; margin: 0px;"  id="tbl_accounts_head">
+                  <thead class="thead-dark"> 
+                    <tr>
+                        <th>Kunden-Nr.</th>
+                        <th>Company / Name</th>
+                        <th>Adresse</th>
+                        <th>E-mail</th>
+                        <th>Telefon</th>
+                  </tr>
+                    </thead>
+                </table>
+              </td>
+            </tr>
+            <tr>
+              <td>
+                <div style="width: 100%; height: 90.5vh; overflow-y: scroll;">            
     <table id="tbl_accounts" class="table table-bordered table-hover table-striped">
-        <thead class="thead-dark"> 
-                <th data-checkbox="true"></th>
-                <th data-sortable="true">Kunden-Nr.</th>
-                <th data-sortable="true">Company / Name</th>
-                <th data-sortable="true">Adresse</th>
-                <th data-sortable="true">E-mail</th>
-                <th data-sortable="true">Telefon</th>
-        <tfoot></tfoot>
         <tbody></tbody>
     </table>
+  </div>
+</td>
+</tr>
+</table> 
     </div>
 <script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
 <script src="../../node_modules/jquery/dist/jquery.min.js"></script>
 <script src="../../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
-<script src="../../node_modules/bootstrap-table/dist/bootstrap-table.min.js"></script>
 <script src="../../node_modules/@fortawesome/fontawesome-free/js/all.min.js"></script>
 <script src="../../js/moduleglobal.js"></script>
 <script src="../../js/database.js"></script>
index f5fa6b5..4088578 100644 (file)
@@ -1,6 +1,7 @@
 var winh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
 var tblh = winh-54;
-
+var shiftdown = false;
+var selection = [];
 function initpage(){
  // console.log(appdb.url);
  
@@ -13,10 +14,9 @@ function loadtable(){
 
   var sql = 'SELECT id, company, prename,surname, address, zip, city, country, email, phone, mobile, ident FROM accounts;';
   var data = appdb.dbquery(sql);
-  console.log(data.sqldata);
+
   for (var i in data.sqldata){
-  var row = '<tr id="' +data.sqldata[i].id + '">'+
-     '<td></td>' +
+  var row = '<tr onclick="setselection(\''+ data.sqldata[i].id +'\');" id="' +data.sqldata[i].id + '">'+
      '<td>' + data.sqldata[i].ident+ '</td>' +
      '<td>' + data.sqldata[i].company + (( data.sqldata[i].company != null)?'<br/>':'') + data.sqldata[i].surname + ' ' + data.sqldata[i].surname + '</td>' +
      '<td>' + data.sqldata[i].address+ ((data.sqldata[i].address != null)?'<br/>':'') + data.sqldata[i].zip + ' ' + data.sqldata[i].city + ((data.sqldata[i].country != null)?'<br/>':'')+ data.sqldata[i].country + '</td>' +
@@ -25,12 +25,14 @@ function loadtable(){
      '</tr>';
      $("#tbl_accounts").append(row.replace(/null/g,''));
   }
-  $('#tbl_accounts').bootstrapTable({
-    pagination: false,
-    search: false,
-    height: tblh,
-    clickToSelect: true
-  });
+  var cols =  $("#tbl_accounts > tbody > tr:first-child").children();
+  var colnum = cols.length -1
+  console.log("childnum:" + colnum);
+  for (var i=1;i<=colnum;i++){
+    wx = $("#tbl_accounts > tbody > tr:first-child > td:nth-child("+ i +")").width();
+    wx = wx +3;
+    $("#tbl_accounts_head > thead > tr > th:nth-child("+ i +")").width(wx);
+  }
   
   
 }
@@ -38,9 +40,8 @@ function loadtable(){
 
 
 function account_edit(){
-  var id= getTableSelectionID();
-  if (id){
-    parent.browserapp.loadmodulepage('accounts','form_account',{"id":d});
+  if (selection.length >= 1){
+    parent.browserapp.loadmodulepage('accounts','form_account',{"id":selection[0]});
   }
   
 }
@@ -54,22 +55,29 @@ function account_duplicate(){
 }
 
 
-function getTableSelectionID(){
-  var sel = $('#tbl_accounts').bootstrapTable('getSelections');
-  var id = null;
-  
-  if (sel){  id=sel[0]._id; }
-  console.log("Selected ID:" + id);
-  return id;
+function setselection(id){
+  if (shiftdown === true){
+    if ($("#" + id).hasClass('highlight')){
+      $("#" + id).removeClass('highlight');  
+      selection.splice($.inArray(id, selection),1);
+    }else {
+      $("#" + id).addClass('highlight');
+      selection.push(id);
+    }
+  } else {
+    $("#" + id).addClass('highlight').siblings().removeClass('highlight');
+    selection = [id];
+  }
 }
 
-function getTableSelectionIDs(){
-  var sel = $('#tbl_accounts').bootstrapTable('getSelections');
-  var ids = [];
-  if (sel){  
-    for (var s in sel){
-      ids.push(s._id);
-    }
+$(document).on('keydown',function(evt){
+  if (evt.key= "Shift"){
+    shiftdown = true;
+  }
+});
+
+$(document).on('keyup',function(evt){
+  if (evt.key = "Shift"){
+    shiftdown = false;
   }
-  return ids;
-}
\ No newline at end of file
+});
\ No newline at end of file
diff --git a/modules/bookings/form_booking.html b/modules/bookings/form_booking.html
new file mode 100644 (file)
index 0000000..09b7480
--- /dev/null
@@ -0,0 +1,260 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta content="width=device-width, initial-scale=1.0" name="viewport">
+  <link href="../../node_modules/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
+  <link href="../../node_modules/bootstrap-table/dist/bootstrap-table.min.css" rel="stylesheet">
+  <link href="../../node_modules/bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css" rel="stylesheet">
+  <link href="../../node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css" rel="stylesheet">
+  <link href="../../css/invoicejournal.epic.css" rel="stylesheet">
+  <link href="../../css/app.css" rel="stylesheet">
+  <title>Rechnung</title>
+</head>
+<body>
+  <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
+    <button class="btn btn-primary" onclick="parent.browserapp.loadmodule('invoices');"><i class="fas fa-chevron-left"></i><br>Back</button> 
+    <a class="navbar-brand" href="#">Rechnung</a>
+    <div class="ml-auto">
+      <div aria-label="Basic example" class="btn-group" role="group">
+        <button class="btn btn-primary" onclick="invoice.new();"><i class="fas fa-plus"></i><br>New</button> 
+        <button class="btn btn-primary" onclick="invoice.delete();"><i class="fas fa-trash"></i><br>Delete</button> 
+        <button class="btn btn-primary" onclick="invoice.clone();"><i class="fas fa-copy"></i><br>Dupl.</button> 
+        <button class="btn btn-primary" onclick="invoice.createpdf();"><i class="fas fa-file-pdf"></i><br>PDF</button> 
+        <button class="btn btn-primary" onclick="invoice.print();"><i class="fas fa-print"></i><br>Print</button> 
+        <button class="btn btn-primary" onclick="invoice.sendinvoice();"><i class="fas fa-envelope"></i><br>Send</button> 
+        <button class="btn btn-primary" onclick="invoice.addfile();"><i class="fas fa-file"></i><br>Add File</button>
+        <div class="btn-group">
+          <button class="btn btn-primary" id="status" type="button">Status</button> 
+          <button aria-expanded="false" aria-haspopup="true" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" type="button"><span class="sr-only">Toggle Dropdown</span></button>
+            <div class="dropdown-menu" id="statusdata"></div> 
+        </div>
+      </div>
+    </div>
+  </nav>
+  <div class="cotainer-fluid" style="margin-top: 52px;">
+    <ul class="nav nav-tabs" id="pagetab" role="tablist">
+      <li class="nav-item"><a aria-controls="invoice" aria-selected="true" class="nav-link active" data-toggle="tab" href="#invoice" id="invoice-tab" role="tab">Rechnung</a></li>
+      <li class="nav-item"><a aria-controls="files" aria-selected="false" class="nav-link" data-toggle="tab" href="#files" id="files-tab" role="tab">Dateien & Bezahlung</a></li>
+      <li class="nav-item"><a aria-controls="notes" aria-selected="false" class="nav-link" data-toggle="tab" href="#notes" id="notes-tab" role="tab">Zusatz-Texte</a></li>
+      
+    </ul>
+    <div class="tab-content" id="tabpagecontent">
+ <div aria-labelledby="invoice-tab" class="tab-pane fade show active" id="invoice" role="tabpanel">
+        <div class="row">
+          <div class="col-md-4">
+            <div class="col-md-12">
+                <div class="form-group row">
+                  <label for="id_sender" class="col-sm-2 col-form-label">Sender</label>
+                  <div class="col-sm-10"> 
+                    <select class="form-control" id="id_sender" name="id_sender"></select>
+                  </div>  
+                </div>
+                <div class="form-group row">
+                  <label for="id_receipient"  class="col-sm-2 col-form-label">Receiver</label> 
+                  <div class="col-sm-10">
+                    <select class="form-control" id="id_receipient" name="id_receipient"></select>
+                  </div>
+                </div>
+                <div class="form-group row">
+                  <label for="payedamount" class="col-sm-2 col-form-label" >Bezahlt</label> 
+                  <div class="col-sm-6 input-group">
+                    <input type="text" class="form-control" id="payedamount" name="payedamount" value="" />
+                    <div class="input-group-append">
+                        <span class="input-group-text">&euro;</span>
+                      </div>
+                  </div>
+                  
+                </div>
+            </div>
+          </div>
+          <div class="col-md-8">
+          <div class="row">
+          <div class="col-md-6">
+            <div class="col-md-12">
+              <div class="form-group row">
+                <label for="date" class="col-sm-2 col-form-label" >Datum</label>
+                <div class="col-sm-4">
+                  <input class="form-control datepicker" id="date" name="date" type="text" >
+                </div>
+                <label for="deadlinedate" class="col-sm-2 col-form-label" >Fälligkeit</label>
+                <div class="col-sm-4">
+                  <input class="form-control datepicker" id="deadlinedate" name="deadlinedate" type="text">
+                </div>
+              </div>
+              <div class="form-group row">
+                <label for="reference" class="col-sm-2 col-form-label" >Referenz</label>
+                <div class="col-sm-10">
+                  <input class="form-control" id="reference" name="reference" type="text">
+                </div>
+              </div>
+            </div>
+          </div>
+          <div class="col-md-6">
+              <div class="col-md-12">
+                <div class="form-group row">
+                  <label for="type" class="col-sm-2 col-form-label">Typ</label>
+                  <div class="col-sm-10"> 
+                    <select class="form-control" id="type" name="type"></select>
+                  </div>  
+                </div>
+                <div class="form-group row">
+                  <label for="id_template" class="col-sm-2 col-form-label">Template</label>
+                  <div class="col-sm-10"> 
+                    <select class="form-control" id="id_template" name="id_template"></select>
+                  </div>  
+                </div>
+              </div>
+          </div>
+          </div>
+          <div class="col-md-12">
+              <div class="row">
+              <div class="col-md-6">
+                  <div class="form-group row">
+                      <label for="netamount" class="col-sm-2 col-form-label" >Netto</label> 
+                      <div class="col-sm-4 input-group">
+                        <input type="text" class="form-control" id="netamount" name="netamount" value="" readonly/>
+                        <div class="input-group-append">
+                          <span class="input-group-text">&euro;</span>
+                        </div>
+                      </div>
+                      <label for="discountamount" class="col-sm-2 col-form-label">Rabatt</label> 
+                      <div class="col-sm-4 input-group">
+                        <input type="text" class="form-control"  id="discountamount" name="discountamount" value="" readonly/>
+                        <div class="input-group-append">
+                          <span class="input-group-text">&euro;</span>
+                        </div>
+                      </div>
+                    </div>
+              </div>
+              <div class="col-md-6">
+                  <div class="form-group row">
+                      <label for="vatamount" class="col-sm-2 col-form-label" >MwSt</label> 
+                      <div class="col-sm-4 input-group">
+                        <input type="text" class="form-control" id="vatamount" name="vatamount" value="" readonly/>
+                        <div class="input-group-append">
+                          <span class="input-group-text">&euro;</span>
+                        </div>
+                      </div>
+                      <label for="grossamount" class="col-sm-2 col-form-label">Brutto</label> 
+                      <div class="col-sm-4 input-group">
+                        <input type="text" class="form-control"  id="grossamount" name="grossamount" value="" readonly/>
+                        <div class="input-group-append">
+                            <span class="input-group-text">&euro;</span>
+                        </div>
+                      </div>
+                    </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        </div>
+        <div class="row">
+          <div class="col-md-12">
+            <nav class="navbar navbar-expand-md navbar-dark bg-dark">
+              <a class="navbar-brand" href="#">Positionen</a>
+              
+              <div class="ml-auto">
+                <div aria-label="Basic example" class="btn-group" role="group">
+                  <button class="btn btn-primary" onclick="position.new();"><i class="fas fa-plus"></i><br> New</button> 
+                  <button class="btn btn-primary" onclick="position.delete();"><i class="fas fa-trash"></i><br> Delete</button> 
+                  <button class="btn btn-primary" onclick="position.clone();"><i class="fas fa-copy"></i><br> Dupl.</button>
+                  <div class="btn-group">
+                    <button class="btn btn-primary" id="status" type="button"><i class="fas fa-box"></i><br> add Product</button> 
+                    <button aria-expanded="false" aria-haspopup="true" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" type="button"><span class="sr-only">Toggle Dropdown</span></button>
+                    <div class="dropdown-menu" id="productdata"></div>
+                  </div>
+                  <div class="btn-group">
+                    <button class="btn btn-primary" id="status" type="button"><i class="fas fa-business-time"></i><br> add Service</button> 
+                    <button aria-expanded="false" aria-haspopup="true" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" type="button"><span class="sr-only">Toggle Dropdown</span></button>
+                    <div class="dropdown-menu" id="servicedata"></div>
+                  </div>
+                </div>
+              </div>
+            </nav>
+          </div>
+        </div>
+        <div class="row">
+          <table class="table table-bordered table-hover table-striped" id="tbl_invoicedata">
+            <thead class="thead-dark">
+              <tr>
+                <th data-checkbox="true"></th>
+                <th data-sortable="true">Description</th>
+                <th data-sortable="true">Qty</th>
+                <th data-sortable="true">Unit</th>
+                <th data-sortable="true">Price</th>
+                <th data-sortable="true">VAT</th>
+                <th data-sortable="true">Discount</th>
+                <th data-sortable="true">Sums</th>
+              </tr>
+            </thead>
+          </table>
+        </div>
+      </div>
+      <div aria-labelledby="files-tab" class="tab-pane fade" id="files" role="tabpanel">
+        <div class="row">
+            <div class="col-md-8">
+                <table class="table table-bordered table-hover table-striped" id="tbl_payements">
+                    <thead class="thead-dark">
+                      <tr>
+                        <th data-checkbox="true"></th>
+                        <th data-sortable="true">Datum</th>
+                        <th data-sortable="true">Typ</th>
+                        <th data-sortable="true">Sender</th>
+                        <th data-sortable="true">Empfänger</th>
+                        <th data-sortable="true">KontoAuszug</th>
+                        <th data-sortable="true">Betrag</th>
+                      </tr>
+                    </thead>
+                  </table>
+            </div>
+          <div class="col-md-4">
+            <table class="table table-bordered table-hover table-striped" id="tbl_files">
+              <thead class="thead-dark">
+                <tr>
+                  <th data-checkbox="true"></th>
+                  <th data-sortable="true">Name</th>
+                  <th data-sortable="true">Type</th>
+                  <th data-sortable="true">Date</th>
+                </tr>
+              </thead>
+            </table>
+          </div>
+          
+        </div>
+      </div>
+      <div aria-labelledby="notes-tab" class="tab-pane fade" id="notes" role="tabpanel">
+        <div class="row">
+          <div class="col-md-12">
+            <div class="form-group">
+              <label for="preface">Vorwort</label>
+              <textarea class="form-control richtextedit" style="height: 200px;" id="preface" name="preface"></textarea>
+            </div>
+          </div>
+        </div>
+        <div class="row">
+          <div class="col-md-12">
+            <div class="form-group">
+              <label for="footnote">Nachwort</label>
+              <textarea class="form-control richtextedit" style="height: 200px;" id="footnote" name="footnote"></textarea>
+            </div>
+          </div>
+        </div>
+      </div>
+      
+    </div>
+  </div>
+  <script> if (typeof module === 'object') {window.module = module; module = undefined;}</script> 
+  <script src="../../node_modules/jquery/dist/jquery.min.js"></script> 
+  <script src="../../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script> 
+  <script src="../../node_modules/bootstrap-table/dist/bootstrap-table.min.js"></script>
+  <script src="../../node_modules/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"></script>  
+  <script src="../../node_modules/@fortawesome/fontawesome-free/js/all.min.js"></script> 
+  <script src="../../node_modules/tinymce/tinymce.min.js"></script> 
+  <script src="../../js/moduleglobal.js"></script> 
+  <script src="../../js/database.js"></script> 
+  <script src="lib/invoice.js"></script> 
+  <script src="form_invoice.js"></script> 
+  <script>if (window.module) module = window.module;</script>
+</body>
+</html>
diff --git a/modules/bookings/form_booking.js b/modules/bookings/form_booking.js
new file mode 100644 (file)
index 0000000..e98ccbc
--- /dev/null
@@ -0,0 +1,81 @@
+function initpage(){
+    $(".datepicker").datepicker({});
+    tinymce.init({
+        selector: 'textarea.richtextedit',
+        branding: false,
+        menubar:false,
+        statusbar: false,
+        plugins: 'searchreplace autolink directionality visualblocks visualchars advlist lists textpattern',
+        toolbar: 'bold italic underline strikethrough forecolor backcolor | link | alignleft aligncenter alignright alignjustify  | numlist bullist outdent indent  | removeformat',
+        image_advtab: true,
+           language: 'de',
+    });
+    getaccounts();
+    if (mpref.cfg.id){
+        getinvoicedata(mpref.cfg.id);
+    } else {
+        //load default new invoice data
+    }
+    console.log("invoice ID:",mpref.cfg.id);
+}
+
+$("#status").onchange(function(data){
+    
+    var xtest = $("#status").html();
+    console.log($("#status").html());
+    if ($("#status").html() == 'bezahlt'){
+        $("#status").attr("class","btn btn-success");
+    } else if ($("#status").html() == 'geplant'){
+        $("#status").attr("class","btn btn-secondary");
+    } else if ($("#status").html() == 'überfällig'){
+        $("#status").attr("class","btn btn-danger");
+    } else if ($("#status").html() == 'verschickt'){
+        $("#status").attr("class","btn btn-warning");
+    } else if ($("#status").html() == 'erhalten'){
+        $("#status").attr("class","btn btn-info");
+    }
+})
+
+function getaccounts(){
+ var sql = "select id, company,case when \"type\" like '%kunde%' then 1 else 0 end as receiver,case when \"type\" like '%lieferant%' or \"type\" like '%mitarbeiter%' or \"type\" like '%behörde%' then 1 else 0 end as sender from accounts;";
+  var data = appdb.dbquery(sql);
+  //onsole.log(data.sqldata);
+  for (var i in data.sqldata){
+    if (data.sqldata[i].receiver == 1){
+        $("#id_receipient").append('<option value="'+data.sqldata[i].id+'">' + data.sqldata[i].company+'</option>');
+    }
+    if (data.sqldata[i].sender == 1){
+        $("#id_sender").append('<option value="'+data.sqldata[i].id+'">' + data.sqldata[i].company+'</option>');
+    }
+  }
+}
+
+function getinvoicedata(id){
+    var sql = "select ij.id_sender,ij.id_receipient,ij.id_template,ij.status,ij.reference,ij.type, " +
+    "ij.statement,STRFTIME(\"%d.%m.%Y\",ij.date) as date,STRFTIME(\"%d.%m.%Y\",ij.deadlinedate) as deadlinedate,ij.footertext,ij.headertext, " +
+    "printf(\"%.2f\",CAST(sum(pos.quantity * pos.unitamount) as real)) as netamount, " +
+    "printf(\"%.2f\",CAST(case when pos.taxpercent > 0 then sum(pos.taxpercent * pos.quantity * pos.unitamount) else 0.0 end AS REAL)) as vatamount, " +
+    "printf(\"%.2f\",CAST(sum(pos.quantity * pos.unitamount) + case when pos.taxpercent > 0 then sum(pos.taxpercent * pos.quantity * pos.unitamount) else 0.0 end AS real)) as grossamount, " +
+    "printf(\"%.2f\",COALESCE(ij.payedamount,0.0)) as payedamount " +
+    "from invoicejournal ij left join invoicepositions pos on (ij.id=pos.id_invoice)  where ij.id='"+mpref.cfg.id+"' group by pos.id_invoice;";
+    var data = appdb.dbquery(sql);
+    if (data.sqldata){
+        for (var i in data.sqldata[0]){
+            if ($("#" + i).prop("tagName") == "SELECT"){
+                $("#" + i).val(data.sqldata[0][i]);
+            } else if ($("#" + i).prop("tagName") == "INPUT" )  {
+                console.log(i + ">=" + data.sqldata[0][i]);
+                $("#" + i).val(data.sqldata[0][i]);
+            } else {
+                $("#" + i).html(data.sqldata[0][i]);
+            }
+
+            $("#" + i).val(data.sqldata[0][i]);
+        }
+    }
+}
+
+function loadinvoicepositions(){
+    
+}
+
diff --git a/modules/bookings/index.html b/modules/bookings/index.html
new file mode 100644 (file)
index 0000000..400fe78
--- /dev/null
@@ -0,0 +1,194 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<link rel="stylesheet" href="../../node_modules/bootstrap/dist/css/bootstrap.min.css">
+<!-- <link rel="stylesheet" href="../../node_modules/bootstrap-table/dist/bootstrap-table.min.css"> -->
+<link rel="stylesheet" href="../../node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css">
+<link rel="stylesheet" href="../../css/invoicejournal.epic.css">
+<link rel="stylesheet" href="../../css/app.css">
+<title>Buchungen</title>
+</head>
+<body>
+        <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
+                <a class="navbar-brand" href="#">Buchungen</a>
+                <div class="ml-auto">
+           <div class="btn-group" role="group" aria-label="Basic example">
+    <button class="btn btn-primary" onclick="booking_new();"><i class="fas fa-plus"></i><br/>New</button>
+    <button class="btn btn-primary" onclick="booking_edit();"><i class="fas fa-edit"></i><br/>Edit</button>
+    <button class="btn btn-primary" onclick="booking_delete();"><i class="fas fa-trash"></i><br/>Delete</button>
+    <button class="btn btn-primary" onclick="booking_clone();"><i class="fas fa-clone"></i><br/>Dupl.</button>
+    <div class="btn-group">
+        <button type="button" class="btn btn-primary" id="seltimerange">TimeRange</button>
+        <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+          <span class="sr-only">Toggle Dropdown</span>
+        </button>
+        <div class="dropdown-menu" id="timerange">
+          
+        </div>
+      </div>
+   </div>
+                </div>
+              </nav>
+    <div class="cotainer-fluid" style="margin-top: 52px;">
+    <table style="width: 100%;" class="noselect">
+      <tr>
+        <td style="padding: 0px; margin: 0px;">
+          <table class="table" style="width: 100%; margin: 0px;"  id="tbl_bookings_head">
+            <thead class="thead-dark"> 
+              <tr>
+              <th><select id="daterange"  class="form-control" onchange="setfilter('daterange','bookingdate');"></select></th>
+              <th><select id="receipient"  class="form-control" onchange="setfilter('receipient','receipient');"></select></th>
+              <th><select id="sender" onchange="setfilter('sender','sender');" class="form-control"></select></th>
+              <th>Beschreibung</th>
+              <th>
+                <select id="status" onchange="setfilter('status','status');" class="form-control">
+                  <option hidden>Status</option>
+                  <option value=""></option>
+                  <option value="bezahlt">bezahlt</option>
+                  <option value="verschickt">verschickt</option>
+                  <option value="geplant">geplant</option>
+                  <option value="überfällig">überfällig</option>
+                </select>
+              </th>
+              <th >Betrag</th>
+            </tr>
+              </thead>
+          </table>
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <div style="width: 100%; height: 80vh; overflow-y: scroll;">
+          <table id="tbl_bookings" style="width: 100%;" class="table table-bordered table-hover table-striped">
+            <!-- <thead class="thead-dark"> <tr>
+              
+                    <th data-checkbox="true"></th>
+                    <th data-sortable="true">Datum</th>
+                    <th data-sortable="true" data-filter="true">Empfänger</th>
+                    <th data-sortable="true">Sender</th>
+                    <th data-sortable="true">Beschreibung</th>
+                    <th data-sortable="true">Status</th>
+                    <th data-sortable="true" data-align="right">Betrag</th>
+                  </tr>
+                    </thead> -->
+            
+            <tbody></tbody>
+          </table>
+        </div>
+        </td>
+      </tr>
+    </table>            
+    <div class="row bg-dark" style="margin: 0px;width: 100%; height: 10vh; overflow:hidden;">
+      <div class="col btn-secondary">
+        <div class="form-group">
+          <label>geplant</label>
+          <div class="form-inline">
+          <div class="input-group" style="margin: 0px  !important;">
+            <div class="input-group-prepend">
+              <div class="input-group-text">&Sigma;</div>
+            </div>
+            <input type="text" class="form-control right" id="cnt_planned"/>
+          </div>
+          <div class="input-group" style="margin: 0px  !important;">
+            <div class="input-group-prepend">
+              <div class="input-group-text">€</div>
+            </div>
+            <input type="text" class="form-control right"  id="sum_planned"/>
+          </div>
+        </div>  
+        </div>
+      </div>
+      <div class="col btn-warning">
+          <div class="form-group">
+            <label>verschickt</label>
+            <div class="form-inline">
+            <div class="input-group" style="margin: 0px  !important;">
+              <div class="input-group-prepend">
+                <div class="input-group-text">&Sigma;</div>
+              </div>
+              <input type="text" class="form-control right" id="cnt_sended"/>
+            </div>
+            <div class="input-group" style="margin: 0px  !important;">
+              <div class="input-group-prepend">
+                <div class="input-group-text">€</div>
+              </div>
+              <input type="text" class="form-control right"  id="sum_sended"/>
+            </div>
+          </div>  
+          </div>
+        </div>
+        <div class="col btn-success">
+            <div class="form-group">
+              <label>bezahlt</label>
+              <div class="form-inline">
+              <div class="input-group" style="margin: 0px  !important;">
+                <div class="input-group-prepend">
+                  <div class="input-group-text">&Sigma;</div>
+                </div>
+                <input type="text" class="form-control right" id="cnt_payed"/>
+              </div>
+              <div class="input-group" style="margin: 0px  !important;">
+                <div class="input-group-prepend">
+                  <div class="input-group-text">€</div>
+                </div>
+                <input type="text" class="form-control right"  id="sum_payed"/>
+              </div>
+            </div>  
+            </div>
+          </div>
+          <div class="col btn-danger">
+              <div class="form-group">
+                <label>überfällig</label>
+                <div class="form-inline">
+                <div class="input-group" style="margin: 0px  !important;">
+                  <div class="input-group-prepend">
+                    <div class="input-group-text">&Sigma;</div>
+                  </div>
+                  <input type="text" class="form-control right" id="cnt_delayed"/>
+                </div>
+                <div class="input-group" style="margin: 0px  !important;">
+                  <div class="input-group-prepend">
+                    <div class="input-group-text">€</div>
+                  </div>
+                  <input type="text" class="form-control right"  id="sum_delayed"/>
+                </div>
+              </div>  
+              </div>
+            </div>
+            <div class="col btn-dark">
+                <div class="form-group">
+                  <label>Total</label>
+                  <div class="form-inline">
+                  <div class="input-group" style="margin: 0px  !important;">
+                    <div class="input-group-prepend">
+                      <div class="input-group-text">&Sigma;</div>
+                    </div>
+                    <input type="text" class="form-control right" id="cnt_total"/>
+                  </div>
+                  <div class="input-group" style="margin: 0px  !important;">
+                    <div class="input-group-prepend">
+                      <div class="input-group-text">€</div>
+                    </div>
+                    <input type="text" class="form-control right"  id="sum_total"/>
+                  </div>
+                </div>  
+                </div>
+              </div>
+            
+    </div>
+    </div>
+<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
+<script src="../../node_modules/jquery/dist/jquery.min.js"></script>
+<script src="../../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
+<!-- <script src="../../node_modules/bootstrap-table/dist/bootstrap-table.min.js"></script> 
+ <script src="../../node_modules/bootstrap-table/dist/extensions/select2-filter/bootstrap-table-select2-filter.min.js"></script> 
+ <script src="../../node_modules/select2/dist/js/select2.full.min.js"></script> -->
+<script src="../../node_modules/@fortawesome/fontawesome-free/js/all.min.js"></script>
+<script src="../../js/moduleglobal.js"></script>
+<script src="../../js/database.js"></script>
+<script src="lib/booking.js"></script>
+<script src="index.js"></script>
+<script>if (window.module) module = window.module;</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/modules/bookings/index.js b/modules/bookings/index.js
new file mode 100644 (file)
index 0000000..e6f2687
--- /dev/null
@@ -0,0 +1,198 @@
+var winh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
+var tblh = winh-54;
+var pagepref = undefined;
+var selection = [];
+// var cfg = {
+//   byear: 2018
+// }
+var sums = { planned: {cnt:0,sum:0},sended: {cnt:0,sum:0},payed: {cnt:0,sum:0},delayed: {cnt:0,sum:0},total: {cnt:0,sum:0}};
+var shiftdown = false;
+function initpage(){
+  console.log(appdb.url);
+  pagepref = parent.usersystem.getPreference("bookings");
+  if (pagepref === null){
+    pagepref= {};
+  }
+  console.log(pagepref);
+  //loadyears();
+  loadtable();
+  load_senders();
+  load_receipients();
+  load_dateranges();
+}
+
+$(document).on('keydown',function(evt){
+  if (evt.key= "Shift"){
+    shiftdown = true;
+  }
+});
+
+$(document).on('keyup',function(evt){
+  if (evt.key = "Shift"){
+    shiftdown = false;
+  }
+});
+
+function loadtable(){
+  var where = '';
+  if (pagepref && pagepref.filter && Object.keys(pagepref.filter).length > 0){
+    var whfilter = [];
+    for (var i in pagepref.filter){
+      console.log(i);
+      if (i == 'bookingdate'){
+        if ($("#daterange :selected").text() != ''){
+          whfilter.push(pagepref.filter[i] + "='" + $("#daterange :selected").text() + "'");
+        }
+        
+      } else {
+        whfilter.push(i + "='" + pagepref.filter[i] + "'");
+      }
+      
+    }
+    if (whfilter.length > 0){
+      where = ' where ' + whfilter.join( " AND "); 
+    }
+    
+  }
+  sums = { planned: {cnt:0,sum:0},sended: {cnt:0,sum:0},payed: {cnt:0,sum:0},delayed: {cnt:0,sum:0},total: {cnt:0,sum:0}};
+  var sql = "select strftime('%d.%m.%Y',invoicepositions.bookingdate) as bookingdate, invoicepositions.bookingdate as datesortable,invoicepositions.id,sender.company as sender,receipient.company as receipient, invoicepositions.description,replace(printf('%.2f€',quantity * unitamount),'.',',') as amount,quantity * unitamount as sumamount , invoicejournal.status from invoicepositions left join accounts sender on (invoicepositions.id_sender=sender.id) left join invoicejournal  on (invoicepositions.id_invoice=invoicejournal.id) left join accounts receipient on (invoicepositions.id_receipient=receipient.id)" + where + " order by invoicepositions.bookingdate desc;";
+  console.log(sql);
+  //$('#tbl_bookings').bootstrapTable('destroy');
+  $("#tbl_bookings > tbody").html("");
+  var data = appdb.dbquery(sql);
+  //console.log(data.sqldata);
+  for (var i in data.sqldata){
+    var cstatus = "secondary";
+    if (data.sqldata[i].status== "geplant"){ 
+      sums.planned.cnt = sums.planned.cnt + 1; 
+      sums.planned.sum = sums.planned.sum + parseFloat(data.sqldata[i].sumamount);  
+    }
+    if (data.sqldata[i].status== "bezahlt"){ 
+      cstatus = "success"; 
+      sums.payed.cnt = sums.payed.cnt + 1;  
+      sums.payed.sum = sums.payed.sum + parseFloat(data.sqldata[i].sumamount); 
+    }
+    if (data.sqldata[i].status== "überfällig"){ 
+      cstatus = "danger"; 
+      sums.delayed.cnt = sums.delayed.cnt + 1;
+      sums.delayed.sum = sums.delayed.sum + parseFloat(data.sqldata[i].sumamount); 
+    }
+    if (data.sqldata[i].status== "verschickt"){ 
+      cstatus = "warning"; 
+      sums.sended.cnt = sums.sended.cnt + 1;
+      sums.sended.sum = sums.sended.sum + parseFloat(data.sqldata[i].sumamount); 
+    }
+    sums.total.cnt = sums.total.cnt + 1; 
+    sums.total.sum = sums.total.sum + parseFloat(data.sqldata[i].sumamount); 
+    var acolor = "";
+    if (data.sqldata[i].amount.startsWith("-")){ acolor = "text-danger"; }
+    var row = '<tr onclick="setselection(\''+ data.sqldata[i].id +'\');" id="' +data.sqldata[i].id + '">'+
+    // '<td></td>' +
+    '<td><span class="d-none">'+data.sqldata[i].datesortable+'</span> ' + data.sqldata[i].bookingdate+ '</td>' +
+    '<td>' + data.sqldata[i].receipient+ '</td>' +
+    '<td>' + data.sqldata[i].sender+ '</td>' +
+    '<td>' + data.sqldata[i].description+ '</td>' +
+    '<td class="btn-'+ cstatus+'">' + data.sqldata[i].status+ '</td>' +
+    '<td class="'+ acolor +' right">' + data.sqldata[i].amount+ '</td>' +
+    '</tr>';
+
+    $("#tbl_bookings > tbody").append(row);
+  }
+  var cols =  $("#tbl_bookings > tbody > tr:first-child").children();
+  var colnum = cols.length -1
+  console.log("childnum:" + colnum);
+  for (var i=1;i<=colnum;i++){
+    wx = $("#tbl_bookings > tbody > tr:first-child > td:nth-child("+ i +")").width();
+    wx = wx +3;
+    $("#tbl_bookings_head > thead > tr > th:nth-child("+ i +")").width(wx);
+  }
+  $("#cnt_planned").val(sums.planned.cnt);$("#sum_planned").val(sums.planned.sum.toFixed(2).toString().replace('.',','));
+  $("#cnt_payed").val(sums.payed.cnt);$("#sum_payed").val(sums.payed.sum.toFixed(2).toString().replace('.',','));
+  $("#cnt_delayed").val(sums.delayed.cnt);$("#sum_delayed").val(sums.delayed.sum.toFixed(2).toString().replace('.',','));
+  $("#cnt_total").val(sums.total.cnt);$("#sum_total").val(sums.total.sum.toFixed(2).toString().replace('.',','));
+  $("#cnt_sended").val(sums.sended.cnt);$("#sum_sended").val(sums.sended.sum.toFixed(2).toString().replace('.',','));
+}
+
+function load_senders(){
+  var sql = "select distinct (sender.company) as sender from invoicepositions left join accounts sender on (invoicepositions.id_sender=sender.id) order by sender.company;";
+  $("#sender").html('<option hidden>Sender</option><option value=""></option>');
+  var data = appdb.dbquery(sql);
+  for (var i in data.sqldata){
+    $("#sender").append('<option value="'+data.sqldata[i].sender+'">'+data.sqldata[i].sender+'</option>');
+  }
+  if (pagepref && pagepref.filter && pagepref.filter.sender && pagepref.filter.sender != ''){
+    //console.log("set selection:" +  pagepref.filter.sender);
+    $("#sender").val(pagepref.filter.sender);
+  }
+}
+
+function load_receipients(){
+  var sql = "select distinct (receipient.company) as receipient from invoicepositions left join accounts receipient on (invoicepositions.id_receipient=receipient.id) order by receipient.company;";
+  $("#receipient").html('<option hidden>Empfänger</option><option value=""></option>');
+  var data = appdb.dbquery(sql);
+  for (var i in data.sqldata){
+    $("#receipient").append('<option value="'+data.sqldata[i].receipient+'">'+data.sqldata[i].receipient+'</option>');
+  }
+  if (pagepref && pagepref.filter && pagepref.filter.receipient && pagepref.filter.receipient != ''){
+    //console.log("set selection:" +  pagepref.filter.receipient);
+    $("#receipient").val(pagepref.filter.receipient);
+  }
+}
+
+function load_dateranges(){
+  var sql = "select strftime('%Y',bookingdate) as daterange, 'strftime(''%Y'',bookingdate)' as filter from invoicepositions where bookingdate is not null group by daterange  union select strftime('%Y %m',bookingdate) as daterange , 'strftime(''%Y %m'',bookingdate)' as filter from invoicepositions where bookingdate is not null group by daterange union  select  strftime('%Y Q',bookingdate)  || (((CAST(strftime('%m',bookingdate) AS INT) -1 ) / 3) +1)   as daterange ,'strftime(''%Y Q'',bookingdate)  || (((CAST(strftime(''%m'',bookingdate) AS INT) -1 ) / 3) +1)' as filter from invoicepositions where bookingdate is not null group by daterange order by daterange desc;";
+  $("#daterange").html('<option hidden>Datum</option><option value=""></option>');
+  var data = appdb.dbquery(sql);
+  for (var i in data.sqldata){
+    $("#daterange").append('<option value="'+data.sqldata[i].filter+'">'+data.sqldata[i].daterange+'</option>');
+  }
+}
+
+
+function setfilter(id,field){
+  console.log("setFilter");
+  if (pagepref['filter'] === undefined){
+    pagepref['filter'] = {};
+  }
+  if ($("#" + id + " :selected").val() != ""){
+    pagepref['filter'][field] = $("#" + id + " :selected").val();
+  }
+  else {
+    delete pagepref['filter'][field];
+    $("#" + id + "").val($("#"+ id +" option:first").val());
+  }
+  parent.usersystem.setPreference('bookings',pagepref);
+  loadtable();
+}
+
+
+function booking_edit(){
+  var inv_id= getTableSelectionID();
+  if (inv_id){
+    parent.browserapp.loadmodulepage('bookings','form_booking',{"id":inv_id});
+  }
+  
+}
+
+function booking_delete(){
+  
+}
+
+function booking_duplicate(){
+  
+}
+
+function setselection(id){
+  if (shiftdown === true){
+    if ($("#" + id).hasClass('highlight')){
+      $("#" + id).removeClass('highlight');  
+    }else {
+      $("#" + id).addClass('highlight');
+    }
+  } else {
+    $("#" + id).addClass('highlight').siblings().removeClass('highlight');
+  }
+}
diff --git a/modules/bookings/lib/booking.js b/modules/bookings/lib/booking.js
new file mode 100644 (file)
index 0000000..e571178
--- /dev/null
@@ -0,0 +1,20 @@
+var invoice = {
+    new: function(){
+        
+    },
+    duplicate: function(id){
+
+    },
+    delete: function(id){
+
+    },
+    update: function(){
+
+    },
+    print: function(){
+
+    },
+    createpdf: function(){
+
+    },
+}
\ No newline at end of file
diff --git a/modules/documents/index.html b/modules/documents/index.html
new file mode 100644 (file)
index 0000000..107fbd9
--- /dev/null
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html lang="en">
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<link rel="stylesheet" href="../../node_modules/bootstrap/dist/css/bootstrap.min.css">
+<!-- <link rel="stylesheet" href="../../node_modules/bootstrap-table/dist/bootstrap-table.min.css"> -->
+<link rel="stylesheet" href="../../node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css">
+<link rel="stylesheet" href="../../css/invoicejournal.epic.css">
+<link rel="stylesheet" href="../../css/app.css">
+<title>Dokumente</title>
+</head>
+<body>
+        <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
+                <a class="navbar-brand" href="#">Dokumente</a>
+                <div class="ml-auto">
+           <div class="btn-group" role="group" aria-label="Basic example">
+    <button class="btn btn-primary" onclick="document_add();"><i class="fas fa-file-upload"></i><br/>Add</button>
+    <button class="btn btn-primary" onclick="document_view();"><i class="fas fa-eye"></i><br/>View</button>
+    <button class="btn btn-primary" onclick="document_parse();"><i class="fas fa-file-import"></i><br/>Parse</button>
+    <button class="btn btn-primary" onclick="document_delete();"><i class="fas fa-trash"></i><br/>Delete</button>
+   </div>
+                </div>
+              </nav>
+    <div class="cotainer-fluid" style="margin-top: 52px;">
+    <table style="width: 100%;" class="noselect">
+      <tr>
+        <td style="padding: 0px; margin: 0px;">
+          <table class="table" style="width: 100%; margin: 0px;"  id="tbl_documents_head">
+            <thead class="thead-dark"> 
+              <tr>
+                <th>Name</th>
+                <th><select id="category" onchange="setfilter('category','category');" class="form-control"><option value="">Kategorie</option></select></th>
+                <th><select id="year" onchange="setfilter('year','year');" class="form-control"><option value="">Jahr</option></select></th>
+                <th><select id="month" onchange="setfilter('month','month');" class="form-control"><option value="">Monat</option></select></th>
+              </tr>
+              </thead>
+          </table>
+        </td>
+      </tr>
+      <tr>
+        <td>
+          <div style="width: 100%; height: 89.5vh; overflow-y: scroll;">
+          <table id="tbl_documents" style="width: 100%;" class="table table-bordered table-hover table-striped">
+            <!-- <thead class="thead-dark"> <tr>
+              
+                    <th data-checkbox="true"></th>
+                    <th data-sortable="true">Datum</th>
+                    <th data-sortable="true" data-filter="true">Empfänger</th>
+                    <th data-sortable="true">Sender</th>
+                    <th data-sortable="true">Beschreibung</th>
+                    <th data-sortable="true">Status</th>
+                    <th data-sortable="true" data-align="right">Betrag</th>
+                  </tr>
+                    </thead> -->
+            
+            <tbody></tbody>
+          </table>
+        </div>
+        </td>
+      </tr>
+    </table>            
+    <div class="row bg-dark" style="margin: 0px;width: 100%; height: 10vh; overflow:hidden;">
+
+    </div>
+    </div>
+<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
+<script src="../../node_modules/jquery/dist/jquery.min.js"></script>
+<script src="../../node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
+<!-- <script src="../../node_modules/bootstrap-table/dist/bootstrap-table.min.js"></script> 
+ <script src="../../node_modules/bootstrap-table/dist/extensions/select2-filter/bootstrap-table-select2-filter.min.js"></script> 
+ <script src="../../node_modules/select2/dist/js/select2.full.min.js"></script> -->
+<script src="../../node_modules/@fortawesome/fontawesome-free/js/all.min.js"></script>
+<script src="../../js/moduleglobal.js"></script>
+<script src="../../js/database.js"></script>
+<script src="index.js"></script>
+<script>if (window.module) module = window.module;</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/modules/documents/index.js b/modules/documents/index.js
new file mode 100644 (file)
index 0000000..fbc1465
--- /dev/null
@@ -0,0 +1,226 @@
+var winh = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
+var tblh = winh-54;
+var pagepref = undefined;
+var selection = [];
+// var cfg = {
+//   byear: 2018
+// }
+// var sums = { planned: {cnt:0,sum:0},sended: {cnt:0,sum:0},payed: {cnt:0,sum:0},delayed: {cnt:0,sum:0},total: {cnt:0,sum:0}};
+shiftdown = false;
+function initpage(){
+  console.log(appdb.url);
+  pagepref = parent.usersystem.getPreference("documents");
+  if (pagepref === null){
+    pagepref= {};
+  }
+  console.log(pagepref);
+  //loadyears();
+  loadtable();
+  load_categories();
+  load_folders();
+}
+
+$(document).on('keydown',function(evt){
+  if (evt.key= "Shift"){
+    shiftdown = true;
+  }
+  console.log("shiftdown: " + shiftdown);
+});
+
+$(document).on('keyup',function(evt){
+  //console.log("shift up x");
+  //console.log(evt.key);
+  if (evt.key = "Shift"){
+    //console.log("shift up");
+    shiftdown = false;
+  }
+  console.log("shiftdown: " + shiftdown);
+});
+
+function loadtable(){
+  // var path = '';
+  // if (pagepref && pagepref.filter && Object.keys(pagepref.filter).length > 0){
+  //    var whfilter = [];
+  //    for (var i in pagepref.filter){
+  //      if (i == 'category' ){
+
+  //      }
+  //      whfilter.push(i + "='" + pagepref.filter[i] + "'");
+  //    }
+  //    path = ' where ' + whfilter.join( " AND "); 
+  // }
+  $("#tbl_documents > tbody").html("");
+  //console.log(decodeURIComponent(mpref.cfg.serviceurl) + 'filesystem/search?type=f&relative=1');
+  
+  $.ajax({
+    encoding:"UTF-8",
+    url:decodeURIComponent(mpref.cfg.serviceurl) + 'filesystem/search?type=f&relative=1',
+    crossDomain: true,
+    success: function (data){
+      console.log(data);
+        result=data.result;
+        for (var i in result){
+          var category = result[i].substring(0,result[i].indexOf("/"));
+          var year = result[i].substring(result[i].indexOf("/")+1,result[i].lastIndexOf("/"));
+          var month = '';
+          if (year.indexOf('/') > 0){
+            month = year.substring(year.indexOf("/")+1);
+            year = result[i].substring(0,year.indexOf("/"));
+          }
+          var file = result[i].substring(result[i].lastIndexOf("/")+1);
+          var row = '<tr onclick="setselection(\'doc_'+ i +'\');" id="doc_'+i+'" data-file="'+result[i]+'">' +
+          '<td>'+file+'</td>' +
+          '<td>'+category+'</td>' +
+          '<td>'+ year +'</td>' +
+          '<td>'+ month +'</td>' +
+          '</tr>'
+          $("#tbl_documents > tbody").append(row);
+        }
+        var cols =  $("#tbl_documents > tbody > tr:first-child").children();
+        var colnum = cols.length -1;
+        console.log("childnum:" + colnum);
+        for (var i=1;i<=colnum;i++){
+          wx = $("#tbl_documents > tbody > tr:first-child > td:nth-child("+ i +")").width();
+          wx = wx +3;
+          $("#tbl_documents_head > thead > tr > th:nth-child("+ i +")").width(wx);
+        }
+      },
+      error: function(data){
+        //alert("Error:" + JSON.stringify(data));
+        console.log("Error:" + JSON.stringify(data));
+      },
+    async:false
+  });
+  
+  
+}
+
+function load_categories(){
+  $.ajax({
+    encoding:"UTF-8",
+    url:decodeURIComponent(mpref.cfg.serviceurl) + 'filesystem/directory/list',
+    crossDomain: true,
+    success: function (data){
+      console.log(data);
+        result=data.result.directory;
+        $("#category").html('<option hidden>Kategorie</option><option value=""></option>');
+        for (var c in result){
+           $("#category").append('<option value="'+result[c]+'">'+result[c]+'</option>');
+         }
+      },
+      error: function(data){
+        //alert("Error:" + JSON.stringify(data));
+        console.log("Error:" + JSON.stringify(data));
+      },
+    async:false
+  });
+
+}
+
+function load_folders(){
+  // var sql = "select distinct (receipient.company) as receipient from invoicepositions left join accounts receipient on (invoicepositions.id_receipient=receipient.id) order by receipient.company;";
+  // $("#receipient").html('<option hidden>Empfänger</option><option value=""></option>');
+  // var data = appdb.dbquery(sql);
+  // for (var i in data.sqldata){
+  //   $("#receipient").append('<option value="'+data.sqldata[i].receipient+'">'+data.sqldata[i].receipient+'</option>');
+  // }
+}
+
+function setfilter(id,field){
+  console.log("setFilter");
+  if (pagepref['filter'] === undefined){
+    pagepref['filter'] = {};
+  }
+  if ($("#" + id + " :selected").val() != ""){
+    pagepref['filter'][field] = $("#" + id + " :selected").val();
+  }
+  else {
+    delete pagepref['filter'][field];
+    $("#" + id + "").val($("#"+ id +" option:first").val());
+  }
+  parent.usersystem.setPreference('documents',pagepref);
+  loadtable();
+}
+
+function document_add(){
+  docfilter = [
+      { name: 'PDF', extensions: ['pdf'] }
+    ];
+  
+  var newfile = parent.usersystem.selectfile("select file",null,docfilter);
+  console.log(newfile);
+}
+
+function document_delete(){
+  
+}
+
+function document_view(){
+  window.open
+}
+
+function document_parse(){
+  
+}
+// function loadyears(){
+//   sql = "select byear from invoicejournal group by byear order by byear asc;";
+//   var data = appdb.dbquery(sql);
+//   //onsole.log(data.sqldata);
+//   for (var i in data.sqldata){
+//     $("#timerange").append('<a class="dropdown-item" href="javascript:change_timerange({"byear":"'+data.sqldata[i].byear+'"});">' + data.sqldata[i].byear+'</a>');
+//   }
+  
+// }
+
+// function booking_edit(){
+//   var inv_id= getTableSelectionID();
+//   if (inv_id){
+//     parent.browserapp.loadmodulepage('bookings','form_booking',{"id":inv_id});
+//   }
+  
+// }
+
+// function invoice_delete(){
+  
+// }
+
+// function invoice_duplicate(){
+  
+// }
+
+function setselection(id){
+  if (shiftdown === true){
+    if ($("#" + id).hasClass('highlight')){
+      $("#" + id).removeClass('highlight');  
+    }else {
+      $("#" + id).addClass('highlight');
+    }
+  } else {
+    $("#" + id).addClass('highlight').siblings().removeClass('highlight');
+  }
+  //console.log("selected:" +id);
+  
+  //.siblings();
+}
+
+
+// function getTableSelectionID(){
+//   var sel = $('#tbl_bookings').bootstrapTable('getSelections');
+//   var id = null;
+  
+//   if (sel){  id=sel[0]._id; }
+//   console.log("Selected ID:" + id);
+//   return id;
+// }
+
+// function getTableSelectionIDs(){
+//   var sel = $('#tbl_bookings').bootstrapTable('getSelections');
+//   var ids = [];
+//   if (sel){  
+//     for (var s in sel){
+//       ids.push(s._id);
+//     }
+//   }
+//   return ids;
+// }
\ No newline at end of file
index 1d78a78..7b1783d 100644 (file)
       "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==",
       "dev": true
     },
+    "almond": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/almond/-/almond-0.3.3.tgz",
+      "integrity": "sha1-oOfJWsdiTWQXtElLHmi/9pMWiiA=",
+      "dev": true
+    },
     "ansi-regex": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
           "version": "2.1.1",
           "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
           "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "aproba": {
           "version": "1.2.0",
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
           "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
           "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
           "dev": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
           "version": "1.1.0",
           "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
           "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "concat-map": {
           "version": "0.0.1",
           "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
           "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
           "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "core-util-is": {
           "version": "1.0.2",
           "version": "2.0.3",
           "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
           "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "ini": {
           "version": "1.3.5",
           "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
           "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
           "dev": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
           "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
           "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
           "dev": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
           "version": "0.0.8",
           "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
           "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "minipass": {
           "version": "2.3.5",
           "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
           "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
           "dev": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.2",
             "yallist": "^3.0.0"
           "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
           "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
           "dev": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
           "version": "1.0.1",
           "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
           "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "object-assign": {
           "version": "4.1.1",
           "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
           "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
           "dev": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
           "version": "5.1.2",
           "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
           "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "safer-buffer": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
           "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
           "dev": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
           "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
           "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
           "dev": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
           "version": "1.0.2",
           "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
           "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "yallist": {
           "version": "3.0.3",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
           "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==",
-          "dev": true,
-          "optional": true
+          "dev": true
         }
       }
     },
       "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==",
       "dev": true
     },
+    "jquery-mousewheel": {
+      "version": "3.1.13",
+      "resolved": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz",
+      "integrity": "sha1-BvAzXxbjU6aV5yBr9QUDy1I6buU=",
+      "dev": true
+    },
     "jsbn": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
         "ajv-keywords": "^3.1.0"
       }
     },
+    "select2": {
+      "version": "4.0.6-rc.1",
+      "resolved": "https://registry.npmjs.org/select2/-/select2-4.0.6-rc.1.tgz",
+      "integrity": "sha1-qmwwOKfw8ukf+t448KIcFeGBMnY=",
+      "dev": true,
+      "requires": {
+        "almond": "~0.3.1",
+        "jquery-mousewheel": "~3.1.13"
+      }
+    },
     "semver": {
       "version": "5.6.0",
       "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
index 8cf47cc..d91c3f1 100644 (file)
@@ -17,6 +17,7 @@
     "jquery": "^3.3.1",
     "pdfjs-dist": "^2.0.943",
     "popper.js": "^1.14.7",
+    "select2": "^4.0.6-rc.1",
     "tinymce": "^5.0.1",
     "tinymce-i18n": "^19.2.11",
     "webpack": "^4.29.4"
index 25583a8..196da85 100644 (file)
@@ -6,13 +6,17 @@ var usersystem = {
         var ppath="";
         if (os.platform() == "darwin"){
             ppath = os.homedir() + '/Library/Application Support/invoicejournal/';
-        } else if (os.platform() == "Win32") {
+        } else if (os.platform() == "win32") {
             ppath = os.homedir() + '/AppData/Roaming/invoicejournal/';
         }
         return ppath;
     },
-    selectfile: function(dlgtitle,lastpath,filefilters=null){
-        return dialog.showOpenDialog({title: dlgtitle,defaultPath: lastpath, filters: filefilters, properties: ['openFile'] });
+    selectfile: function(dlgtitle,lastpath,filefilters=null,multiselect=false){
+        var props = ['openFile'];
+        if (multiselect == true){
+          props.push('multiSelections');
+        }
+        return dialog.showOpenDialog({title: dlgtitle,defaultPath: lastpath, filters: filefilters, properties: props });
     },
     selectdir: function(dlgtitle,lastpath){
         return dialog.showOpenDialog({title: dlgtitle,defaultPath: lastpath, filters: filefilters, properties: ['openDirectory'] });
@@ -30,7 +34,8 @@ var usersystem = {
         dialog.showErrorBox(errtitle, errmsg);
     },
     setPreference: function(key,data){
-        //console.log("save preferences to: " + this.profilepath()  + key + ".json");
+        console.log("sset preference to: " + this.profilepath()  + key + ".json");
+        console.log(data);
         if ((typeof data == 'object') || (typeof data == 'array')){
             data = JSON.stringify(data);
         }
@@ -38,6 +43,7 @@ var usersystem = {
         return result;
     },
     getPreference: function(key){
+      console.log("getPreference:" + this.profilepath()  + key + ".json");
         var data = null;
         if (fs.existsSync(this.profilepath()  + key + ".json")){
             console.log("Read Key:" + key);
@@ -51,6 +57,8 @@ var usersystem = {
     },
     getLocalDataSets: function(){
         var datasets =[];
+        console.log(os.platform());
+        console.log("ProfilePath:" + this.profilepath());
         var files = fs.readdirSync(this.profilepath());
         files.forEach(function(file) {
             if (file.match('db\..*\.json')){
diff --git a/server/Module/FileSystem.pm b/server/Module/FileSystem.pm
new file mode 100644 (file)
index 0000000..bb3d150
--- /dev/null
@@ -0,0 +1,315 @@
+package Module::FileSystem;
+
+use strict;
+use warnings;
+use parent qw(Plack::Component);
+use Plack::Request;
+use Data::Dumper;
+use File::Find::Rule;
+use File::Basename;
+use JSON::PP;
+use File::Path qw(make_path remove_tree);
+use File::Copy;
+use MIME::Types;
+if ($^O eq "MSWin32"){
+       eval('use Win32::File;');
+}
+
+sub call {
+    my($self, $env) = @_;
+      if (($env->{REMOTE_ADDR} =~ "^127\.0\.") && 
+               ($env->{REMOTE_ADDR} =~ "^10\.") && 
+               ($env->{REMOTE_ADDR} =~ "^172\.16\.") && 
+               ($env->{REMOTE_ADDR} =~ "^192\.168\.")) {
+               return [
+       404,
+       [ 'Content-Type' => "text/html",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+               [ "Sorry no remote access allowed!" ]
+       ];
+       }
+       if ($env->{PATH_INFO} =~ /^\/search/){
+               return $self->search($env);     
+       } elsif ($env->{PATH_INFO} =~ /^\/directory/) {
+               return $self->directory($env);
+       } elsif ($env->{PATH_INFO} =~ /^\/file/) {
+               return $self->file($env);
+       } elsif ($env->{PATH_INFO} =~ /^\/userenv/){
+               return $self->userenv($env);
+       }
+       return [
+       404,
+       [ 'Content-Type' => "text/html",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+               [ "Unknown System Request!" ]
+       ];
+}
+
+sub search() {
+       my $self = shift;
+       my $env = shift;
+       my $html->{result} = ();
+    # $html->{docroot} = $self->{docpath};
+       my $ct="application/json";
+       my $status=200;
+       my $req = Plack::Request->new($env);
+       my $ff  = File::Find::Rule->new;
+    if (exists($req->query_parameters->{name})){
+               my $namesearch = $req->query_parameters->{name};
+       $ff->name($req->query_parameters->{name})
+    }elsif((exists($req->query_parameters->{namelist}))){
+                       my @nl = split(",",$req->query_parameters->{namelist});
+                       $ff->name(@nl);
+               }
+    if (exists($req->query_parameters->{type})){
+       if ($req->query_parameters->{type} eq 'd'){
+               $ff->directory; 
+       } else {
+               $ff->file;
+       }
+    }
+    if (exists($req->query_parameters->{relative})){
+       $ff->relative;
+    }
+    if (exists($req->query_parameters->{osspec})){
+       $ff->canonpath;
+    }
+    my $spath = $self->{docpath};
+    if (exists($req->query_parameters->{path})){
+
+      $spath = $self->{docpath}.'/'.$req->query_parameters->{path};
+      $spath =~ s/..\///g;
+    }
+    my @data = $ff->in($spath);
+    if (exists($req->query_parameters->{sorted})){
+       @data = sort {$a cmp $b} @data;
+       if ($req->query_parameters->{sorted} eq "desc"){
+               @data = reverse(@data);
+       } 
+    }
+               my $ret = undef;
+               # if (exists($req->query_parameters->{output}) && $req->query_parameters->{output} eq "text"){
+               #       $ct = "text/plain";
+               #       $ret = "";
+               #       foreach my $d  (@data){
+               #               $ret .= $d."\n";
+               #       }
+               # } 
+    # elsif (exists($req->query_parameters->{output}) && $req->query_parameters->{output} eq "fmlist"){
+               #       $ct = "text/plain";
+               #       $ret = "";
+               #       foreach my $d  (@data){
+               #               $ret .= $d."\r";
+               #       }
+               # } 
+               # else {
+                       $html->{result} = \@data;
+                       $ret = JSON::PP::encode_json($html);
+               # }
+    
+    return [
+    200,
+     [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+     [ $ret ]
+  ];
+}
+
+sub directory() {
+       my $self = shift;
+       my $env = shift;
+       my $html;
+       my $ct="application/json";
+       my $status=200;
+       
+       my $req = Plack::Request->new($env);
+       if ($env->{PATH_INFO} =~ /^\/directory\/list/){
+       my $mt = MIME::Types->new();
+       $html->{result} = [];
+      my $dir=$self->{docpath};
+      if (exists($req->query_parameters->{path})){
+        $dir=$self->{docpath}.'/'.$req->query_parameters->{path};
+        $dir =~ s/..\///g;
+      }
+       $html->{result} = {};
+       if (-d $dir){
+               my @dirs = ();
+               my @files = ();
+               opendir(LDIR,$dir);
+               while (my $f = readdir(LDIR)){
+                       if ($f =~ /^\./){ next; }
+                       
+                       if (-d $dir.'/'.$f){
+                               my $bok =1 ;
+                               if ($^O eq "MSWin32"){
+                                       eval ('my $attr;
+                                       Win32::File::GetAttributes($dir.\'/\'.$f,$attr);
+                                       if ($attr & HIDDEN){
+                                               $bok = 0;
+                                       }');
+                               }
+                               if ($bok == 1){
+                                       push(@dirs,$f); 
+                               }
+                               
+                       } elsif (-f $dir.'/'.$f) {
+                               my $bok =1 ;
+                               if ($^O eq "MSWin32"){
+                                       eval ('my $attr;
+                                       Win32::File::GetAttributes($dir.\'/\'.$f,$attr);
+                                       if ($attr & HIDDEN){
+                                               $bok = 0;
+                                       }');
+                               }
+                               if ($bok == 1){
+                                       print $f."\n";
+                                       my $fi->{name} = $f;
+                                       my $mtf = $mt->mimeTypeOf($f);
+                                       $fi->{mimetype} = (exists($mtf->{MT_simplified})?$mtf->{MT_simplified}:'unknown');  
+       
+                                       push(@files,$fi);
+                               }
+                       }
+               }
+               closedir(LDIR);
+               $html->{result}->{directory} = \@dirs;
+               $html->{result}->{file} = \@files;
+    }
+    
+  }
+       if ($env->{PATH_INFO} =~ /^\/directory\/exists/){
+       $html->{result} = 0;
+       if (exists($req->query_parameters->{path})){
+               if (-d $req->query_parameters->{path}){
+               $html->{result} = 1;
+               }
+       }
+    }
+    if ($env->{PATH_INFO} =~ /^\/directory\/make/){
+       make_path($req->query_parameters->{path});
+       $html->{result} = 0;
+       if (-d $req->query_parameters->{path}){
+               $html->{result} = 1;
+       }
+       }
+       if ($env->{PATH_INFO} =~ /^\/directory\/delete/){
+       my $keep_root = 0;
+       if (exists($req->query_parameters->{keep_root})){
+               $keep_root = 1;
+       }
+       $html->{result} = 0;
+       if (-d $req->query_parameters->{path}){
+               remove_tree( $req->query_parameters->{path}, {keep_root => $keep_root} );
+               $html->{result} = 1;
+       }
+       }
+       return [
+    200,
+     [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+
+sub file() {
+       my $self = shift;
+       my $env = shift;
+       my $html->{result} = ();
+       my $ct="application/json";
+       my $status=200;
+       
+       my $req = Plack::Request->new($env);
+       if ($env->{PATH_INFO} =~ /^\/file\/exists/){
+       $html->{result} = 0;
+       if (exists($req->query_parameters->{path})){
+               if (-f $req->query_parameters->{path}){
+               $html->{result} = 1;
+           }
+       }
+    }
+#    if ($env->{PATH_INFO} =~ /^\/file\/choose/){
+#              # A simple open file with graphic filers
+#              my ( @fss, $fss );
+#              my ( @parms );
+#              push @parms,
+#                      -filter => [ 'PDF - fichiers PDF', '*.pdf' ],
+#                      -directory => $ENV{HOME},
+#                      -title => 'selectionner un fichier PDF';
+#                      @fss = Win32::GUI::GetOpenFileName ( @parms );
+#                      if (scalar(@fss) > 0 ){
+#                               $html->{result} = \@fss;
+#                      }
+#        }
+    if ($env->{PATH_INFO} =~ /^\/file\/write/){
+               $html->{result} = 0;
+       if (exists($req->query_parameters->{path})){
+           if (! -d (dirname($req->query_parameters->{path}))){
+               make_path(dirname($req->query_parameters->{path}))
+           }
+           my $fwrite = ">";
+           if (exists($req->query_parameters->{append})){
+               $fwrite = ">>"; 
+           }
+           my $datax = $req->body_parameters->{data};
+           print $req->body_parameters->{data}."\n";
+           open(WFI,$fwrite.$req->query_parameters->{path});
+               print WFI $req->body_parameters->{data};
+           close(WFI);
+           $html->{result} = 1;
+          }
+         }
+         if ($env->{PATH_INFO} =~ /^\/file\/read/){
+               $html->{result} = "";
+          if (exists($req->query_parameters->{path})){
+           if (-f $req->query_parameters->{path}){
+               my $rdata = "";
+               open(RFI,$req->query_parameters->{path});
+               while ( my $l = <RFI>){
+                       $rdata .= $l; 
+               } 
+               close(RFI);
+               $html->{result} = $rdata;
+           }
+          }
+         }
+         if ($env->{PATH_INFO} =~ /^\/file\/copy/){
+               $html->{result} = "";
+          if (exists($req->query_parameters->{src})){ 
+           if (-f $req->query_parameters->{src}){
+               my $dest = $req->query_parameters->{dest};
+               if (! -d dirname($req->query_parameters->{dest})){
+                       make_path(dirname($req->query_parameters->{dest}))
+               }
+               if ($req->query_parameters->{src} ne $req->query_parameters->{dest}){
+                       my $cp = copy($req->query_parameters->{src},$req->query_parameters->{dest});
+                       $html->{result} = $cp;  
+               } else {
+                       $html->{result} = 1;
+               }
+           }
+          }
+   }
+
+  return [
+    200,
+     [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+
+
+sub userenv() {
+       my $self = shift;
+       my $env = shift;
+       my $html->{result} = ();
+       my $ct="application/json";
+       my $status=200;
+       
+       my $req = Plack::Request->new($env);
+       foreach my $k (keys(%ENV)){
+               $html->{result}->{$k} = $ENV{$k};
+       }
+        
+       return [
+    200,
+     [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+1;
\ No newline at end of file
diff --git a/server/Module/OpenVPN.pm b/server/Module/OpenVPN.pm
new file mode 100644 (file)
index 0000000..4267ef6
--- /dev/null
@@ -0,0 +1,207 @@
+package Module::OpenVPN;
+
+use strict;
+use warnings;
+use parent qw(Plack::Component);
+use Plack::Request;
+use Data::Dumper;
+use File::Find::Rule;
+use File::Basename;
+use JSON::PP;
+use File::Copy;
+use File::Path qw(make_path);
+
+sub call {
+    my($self, $env) = @_;
+    #$self->_app->($env);
+    if (($env->{REMOTE_ADDR} =~ "^127\.0\.") && 
+               ($env->{REMOTE_ADDR} =~ "^10\.") && 
+               ($env->{REMOTE_ADDR} =~ "^172\.16\.") && 
+               ($env->{REMOTE_ADDR} =~ "^192\.168\.")) {
+               return [
+       404,
+       [ 'Content-Type' => "text/html",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+               [ "Sorry no remote access allowed!" ]
+       ];
+       }
+       if ($env->{PATH_INFO} =~ /^\/connect/){
+       return $self->vpnconnect($env);
+    } elsif ($env->{PATH_INFO} =~ /^\/disconnect/){
+       return $self->vpndisconnect($env);
+    } elsif ($env->{PATH_INFO} =~ /^\/installprofile/){
+       return $self->vpninstallprofile($env);
+    } elsif ($env->{PATH_INFO} =~ /^\/listprofiles/){
+       return $self->vpnprofilelist($env);
+    }
+    return [
+       404,
+       [ 'Content-Type' => "text/html",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+               [ "Unknown System Request!" ]
+       ];
+}
+
+sub vpnconnect(){
+       my $self = shift;
+       my $env = shift;
+       my $html->{result} = 0;
+       my $req = Plack::Request->new($env);
+       my $uprofile = "";
+       #is gui or vpn running
+       
+       if (exists($req->query_parameters->{vpnprofile})){
+               my $status = $self->vpnstatus();
+               if (!exists($status->{active}->{$req->query_parameters->{vpnprofile}})){
+                       if ($^O eq "MSWin32"){
+                               if (exists($status->{gui})){
+                                       system('taskkill.exe /F /IM openvpn.exe');
+                                       system('taskkill.exe /F /IM openvpn-gui.exe');
+                                       sleep(1);
+                               }
+                               my $st = system('start /b "" "C:\Program Files\OpenVPN\bin\openvpn-gui.exe" --connect '.$req->query_parameters->{vpnprofile}.'.ovpn');
+                               if ($st == 0){
+                                       
+                                       my $bconn = 0;
+                                       my $i = 30;
+                                       while ($bconn == 0 || $i > 0){
+                                               $status = $self->vpnstatus();
+                                               if (exists($status->{active}->{$req->query_parameters->{vpnprofile}})){
+                                                       $html->{result} = $status;
+                                                       $bconn = 1;
+                                               }
+                                               $i--;
+                                               sleep(1);
+                                       }
+                               }
+                       }       
+               } else {
+                       $html->{result} = $status;
+               }
+       }
+       return [
+    200,
+     [ 'Content-Type' => "application/json",'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+
+sub vpnstatus(){
+       my $self = shift;
+       my $status = ();
+       if ($^O eq "MSWin32"){
+               
+               my $tasklist = `tasklist`;
+               my @task = split("\n",$tasklist);
+               my @ovpntasks = grep(/openvpn-gui\.exe/,@task);
+               if (scalar(@ovpntasks) > 0){
+                       $status->{gui} = "running";
+               }
+               @ovpntasks = grep(/openvpn.exe/,@task);
+               #$status->{active_connections} = scalar(@ovpntasks);
+               if (scalar(@ovpntasks) > 0){
+                       my $ff = File::Find::Rule->new();
+                       $ff->file;
+                       $ff->name('*.log');
+                       my @loglist =$ff->in($ENV{USERPROFILE}.'/OpenVPN/log');
+                       foreach my $c (@loglist){
+                               open(CLOG,$c);
+                               my @data = <CLOG>;
+                               close(CLOG);
+                               my $laststate=$data[scalar(@data)-1];
+                               chomp($laststate);
+                               if ($laststate =~ /CONNECTED/){
+                                       my ($time,$ip,$server,$port) = $laststate =~ /.+MANAGEMENT:\s>STATE:(\d+),CONNECTED,SUCCESS,(.+),(.+),(.+),,$/;
+                                       if (!exists($status->{connection}->{$ip})){
+                                               $status->{connection}->{$ip}->{config} = substr(basename($c),0,-4);;
+                                               $status->{connection}->{$ip}->{server} = $server; 
+                                               $status->{connection}->{$ip}->{port} = $port;
+                                               $status->{connection}->{$ip}->{connected_since} = $time;
+                                       }else {
+                                               if ($time >= $status->{connection}->{$ip}->{connected_since}){
+                                                       $status->{connection}->{$ip}->{config} = substr(basename($c),0,-4);
+                                                       $status->{connection}->{$ip}->{server}= $server; 
+                                                       $status->{connection}->{$ip}->{port} = $port;
+                                                       $status->{connection}->{$ip}->{connected_since} = $time;
+                                               }
+                                       }
+                               }
+                       }
+                       my @notactive = ();
+                       my $active = ();
+                       foreach my $c (keys(%{$status->{connection}})){
+                               my $routeslist = `route print -4`;      
+                               my @routes = split("\n",$routeslist);
+                               my @activetest = grep(/$c/,@routes);
+                               if (scalar(@activetest) == 0){
+                                       push @notactive,$c;
+                               } else {
+                                       $active->{$status->{connection}->{$c}->{config}} = $c;
+                               }
+                       }
+                       foreach my $na (@notactive){
+                               delete $status->{connection}->{$na};
+                       }
+                       $status->{active} = $active;
+               }
+       }
+       return $status;
+}
+
+sub vpndisconnect(){
+       my $self = shift;
+       my $env = shift;
+       my $html->{result} = 1;
+       if ($^O eq "MSWin32"){
+               system('taskkill.exe /F /IM openvpn.exe');
+               system('taskkill.exe /F /IM openvpn-gui.exe');
+       }
+       return [
+    200,
+     [ 'Content-Type' => "application/json",'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+
+sub vpninstallprofile(){
+       my $self = shift;
+       my $env = shift;
+       my $req = Plack::Request->new($env);
+       my $html->{result} = 0;
+       if ($^O eq "MSWin32"){
+               if ( ! -d $ENV{USERPROFILE}.'/OpenVPN'){
+                       make_path($ENV{USERPROFILE}.'/OpenVPN');
+               }
+               if (exists($req->query_parameters->{vpnprofile}) && (-e $req->query_parameters->{vpnprofile}) && ($req->query_parameters->{vpnprofile} =~ /\.ovpn$/)){
+                       copy(req->query_parameters->{vpnprofile},$ENV{USERPROFILE}.'/OpenVPN/'.basename($req->query_parameters->{vpnprofile}));
+                               $html->{result} = 1;
+               }
+       }
+       return [
+    200,
+     [ 'Content-Type' => "application/json",'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+
+sub vpnprofilelist(){
+       my $self = shift;
+       my $env = shift;
+       my $html->{result} = ();
+       if ($^O eq "MSWin32"){
+               my $ff = File::Find::Rule->new();
+               $ff->file;
+               $ff->name('*.ovpn');
+               my @vpnlist =$ff->in($ENV{USERPROFILE}.'/OpenVPN');
+               foreach (my $p=0;$p<scalar(@vpnlist);$p++){
+                       $vpnlist[$p] = substr(basename($vpnlist[$p]),0,-5);
+               } 
+               $html->{result} = \@vpnlist;
+       }
+       return [
+    200,
+     [ 'Content-Type' => "application/json",'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+
+
+1;
\ No newline at end of file
diff --git a/server/Module/PDFExtract.pm b/server/Module/PDFExtract.pm
new file mode 100644 (file)
index 0000000..393a1f9
--- /dev/null
@@ -0,0 +1,264 @@
+package Module::PDFExtract;
+
+use strict;
+use warnings;
+use parent qw(Plack::Component);
+use Plack::Request;
+use File::Basename;
+use Data::Dumper;
+use PDF::API2; 
+use File::Path qw/make_path/;
+
+sub call {
+    my($self, $env) = @_;
+    #$self->_app->($env);
+    my $html->{result} = "unknown function";
+    if (($env->{REMOTE_ADDR} =~ "^127\.0\.") && 
+               ($env->{REMOTE_ADDR} =~ "^10\.") && 
+               ($env->{REMOTE_ADDR} =~ "^172\.16\.") && 
+               ($env->{REMOTE_ADDR} =~ "^192\.168\.")) {
+               return [
+       404,
+       [ 'Content-Type' => "text/html",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+               [ "Sorry no remote access allowed!" ]
+       ];
+       }
+       # if ($env->{PATH_INFO} =~ /^\/pdfsplit/){
+       #       return $self->pdfsplit($env);
+       # }
+       # if ($env->{PATH_INFO} =~ /^\/pdfpagenumbers/){
+       #       return $self->pdfpagesnumbers($env);
+       # }
+#      if ($env->{PATH_INFO} =~ /^\/pdfextract/){
+#              return $self->pdfextract($env);
+#      }
+       if ($env->{PATH_INFO} =~ /^\/parse/){
+               return $self->parsestatement($env);
+       }
+       # if ($env->{PATH_INFO} =~ /^\/parsestatement/){
+       #       return $self->parsestatement($env);
+       # }
+       return [
+    404,
+     [ 'Content-Type' => 'text/html','Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+     [ "unknown function" ]
+  ];
+}
+
+# sub pdfpagesnumbers(){
+#      my $self = shift;
+#      my $env = shift;
+#      my $ct="application/json";
+#      my $status=200;
+#      my $req = Plack::Request->new($env);
+#      my $html->{result}->{pagenumbers} = 0;
+#      if (exists($req->query_parameters->{file}) && ($req->query_parameters->{file} =~ /\.pdf$/)){
+#              my $pdf = PDF::API2->open($req->query_parameters->{file});
+#              $html->{result}->{pagenumbers} = $pdf->pages;
+#      }
+#      return [
+#     200,
+#      [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+#      [ JSON::PP::encode_json($html) ]
+#   ];
+# }
+
+# sub pdfsplit(){
+#      my $self = shift;
+#      my $env = shift;
+#      my $html->{result} = ();
+#      my $ct="application/json";
+#      my $status=200;
+#      my $req = Plack::Request->new($env);
+#      my @nfiles = ();
+#      my $outputdir = $ENV{TEMP};
+#      if (exists($req->query_parameters->{file}) && exists($req->query_parameters->{prefix})){
+#              my $basepdf = basename($req->query_parameters->{file});
+#              $outputdir =~ s/\\/\//g;
+               
+#              my $oldpdf = PDF::API2->open($req->query_parameters->{file});
+#              my $xx = $oldpdf->pages;
+#              for my $page_nb (1..$xx) {
+#                      my $newpdf = PDF::API2->new;
+#                      my $page = $newpdf->importpage($oldpdf, $page_nb);
+                       
+#                      my $npdfname = $outputdir.'/'.$req->query_parameters->{prefix}.substr($basepdf,0,-4).".".$page_nb.".pdf"; 
+#                      push @nfiles,$npdfname;
+#                      if (-e $npdfname){  unlink($npdfname); }
+#                      $newpdf->saveas($npdfname);
+#              }
+#      }
+#      foreach my $n (@nfiles){
+#              my $r  = $self->pdfextract($n);
+#      }
+#      $html->{result}->{files} = \@nfiles;
+#      return [
+#     200,
+#      [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+#      [ JSON::PP::encode_json($html) ]
+#   ];
+# };
+
+sub pdfextract(){
+       my $self = shift;
+       my $file = shift;
+#      my $html->{result} = ();
+#      my $ct="application/json";
+#      my $status=200;
+#      my $req = Plack::Request->new($env);
+       my $pdftotext;
+       my $sep = "/";
+       if ($^O eq "MSWin32") {
+               $sep = "\\";
+               $pdftotext=dirname($0).$sep.'pdftotext.exe';
+       }else {
+               $pdftotext=dirname($0).$sep.'pdftotext';
+       }
+       if (-e $file.'.txt'){
+       unlink($file.'.txt');
+    }
+       my $cmd = 'start /b "" "'.$pdftotext.'" -q -table -eol unix "'.$file.'" "'.$file.'.txt"';
+       my $st = `$cmd`;#'system(1,$cmd)' ;
+       #print $cmd."->".$st."\n";
+       return $st;
+}
+
+
+sub parsestatement(){
+  my $self = shift;
+  my $env = shift;
+  my $html->{result} = ();
+  my $req = Plack::Request->new($env);
+  my $ct="application/json";
+  my $status=200;
+  my $file = $self->{docpath}.'/'.$req->query_parameters->{file};
+  if (! -e $file){
+    $html->{error}->{msg} = "file: ".$file." does not exist!";
+    return [
+    200,
+     [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+  }
+  
+  
+  my $pxdata = ();
+   my @pdata = ();
+   my $jdata = ();
+  if (-e $file){
+    my $st = $self->pdfextract($file);
+         
+    if (($st == 0) && (-e $file.".txt")){
+      open(PDFDATA,$file.".txt");
+      while (my $l = <PDFDATA>) {
+        chomp($l);
+        if ($l ne "") {push @pdata,$l;}
+      }
+      close(PDFDATA);
+    }
+    my $caccount = "";
+    my $stmtnum = "";
+    my $r = 0;
+    my $cpos = "";
+    foreach my $p (@pdata){
+      if ($p =~ /^\s+Konto\s+:/ ) {
+        $cpos = "";
+        ($caccount) = $p =~ m/.+IBAN\s+(.+)$/;
+        next;
+      }elsif ($p =~ /Kontoauszug Nr\./){
+        $cpos = "";
+        ($stmtnum) = $p =~ m/^Kontoauszug Nr\.\s(\d+).+$/;
+        next;
+      }elsif ($p =~ /^\d\d\.\d\d\s+[GUT|UEBER|SEPA]/){
+        $cpos = "";
+        $r++;
+        my ($type,$trdate,$trval,$trsign) = $p =~ m/\d\d\.\d\d\s+(.+)\s+(\d\d\.\d\d\.\d\d)\s+([\d|,|\.]+)\s([+|-])$/; 
+        $type =~ s/\s//g;
+        $trsign =~ s/\+//;
+        $trval=~ s/\.//g;
+        $trdate = substr($trdate,0,6).'20'.substr($trdate,-2);
+        $jdata->{$r}->{"Account"} = $caccount;
+        $jdata->{$r}->{"StatementNumber"} = $stmtnum;
+        $jdata->{$r}->{"BookingDate"} = $trdate;
+        $jdata->{$r}->{"Amount"} = $trsign.$trval;
+        $jdata->{$r}->{"TransactionIdent"} = "";
+        $jdata->{$r}->{"Message"} = "";
+        $jdata->{$r}->{"ForeignAccountOwner"} = "";
+        $jdata->{$r}->{"Bank"} = "";
+        $jdata->{$r}->{"TransferAccount"} = "";
+        $jdata->{$r}->{"TransferCosts"} = 0;
+        #$jdata->{$r}->{"BookingType"} = $type;
+        next;
+      }elsif ($p =~ /^\s+Unser Zeichen/){
+        $cpos = "TransactionIdent";
+        my ($trid) = $p =~ m/^\s+Unser Zeichen\s+(.+)$/;
+        $jdata->{$r}->{$cpos} =$trid;
+      }elsif ($p =~ /^\s+Mitteilung/){
+        $cpos = "Message";
+        my ($msg) = $p =~ m/^\s+Mitteilung\s+(.+)$/;
+        $jdata->{$r}->{$cpos} = $msg;
+      }elsif ($p =~ /^\s+Auftraggeber/){
+        $cpos = "ForeignAccountOwner";
+        my ($apl) = $p =~ /^\s+Auftraggeber\s+(.+)$/;
+        $apl =~ s/\s+/\ /g;
+        $jdata->{$r}->{$cpos} =$apl;
+      }elsif ($p =~ /^\s+Bank d. Auftr.gebers/){
+        $cpos = "";
+      }elsif ($p =~ /^\s+BIC-Code Bank d. Auftraggebers/){
+        $cpos = "Bank";
+        my ($trfbank) = $p =~ /^\s+BIC-Code\sBank\sd\.\sAuftraggebers\s+(.+)$/;
+        $jdata->{$r}->{$cpos} =$trfbank;
+      }elsif ($p =~ /^\s+End-to-End-Identifizierung/){
+        $cpos = "";
+      }elsif ($p =~ /^\s+Beg.nstigter/){
+        $cpos = "ForeignAccountOwner";
+        my ($recp) = $p =~ /^\s+Beg.nstigter\s+(.+)$/;
+        $recp =~ s/\s+/\ /g;
+        $jdata->{$r}->{$cpos} =$recp;
+        $cpos="";
+      }elsif ($p =~ /^\s+Konto Nr. Beg.nst./){
+        $cpos = "TransferAccount";
+        
+        my ($trfacc) = $p =~ /^\s+Konto\sNr\.\sBeg.nst.\s+(.+)$/;
+        $trfacc =~ s/\///g;
+        $trfacc =~ s/(....)/$1 /sg;
+        $trfacc =~ s/\s+$//;
+        $jdata->{$r}->{$cpos} =$trfacc;
+        $cpos="";
+      }elsif ($p =~ /^\s+bei/){
+        $cpos = "";
+      }elsif ($p =~ /^\s+Transfergeb.hr/){
+        $cpos = "TransferCosts";
+        my ($tramount) = $p =~ /^\s+Transfergeb.hr\s+EUR\s+(.+)$/;
+        $tramount =~ s/\,/\./g;
+        $jdata->{$r}->{$cpos} =$tramount;
+      }elsif ($p =~ /^\s+Durch Ihren Bonus abgedeckt/){
+        $cpos = "TransferCosts";
+        my ($tramount) = $p =~ /^\s+Durch Ihren Bonus abgedeckt\s+EUR\s+(.+)$/;
+        $tramount =~ s/\,/\./g;
+        $jdata->{$r}->{$cpos} = $jdata->{$r}->{$cpos} + $tramount;
+      }elsif ($p =~ /^\s+Zeichen/){
+        $cpos = "";
+      } elsif ($p =~ /^\s+Neuer Kontostand/){
+        $cpos="";
+      }elsif ($cpos ne "") {
+        my ($data) = $p =~ m/\s+(.+)$/;
+        $jdata->{$r}->{$cpos} .= " ".$data;
+      }
+    }
+
+  }
+  if (-e $file.'.txt'){
+    unlink($file.'.txt');
+  }
+  $html->{result} = $jdata;
+  return [
+    200,
+     [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+
+
+1;
\ No newline at end of file
diff --git a/server/Module/PDFExtract_checkservice.pm b/server/Module/PDFExtract_checkservice.pm
new file mode 100644 (file)
index 0000000..018361d
--- /dev/null
@@ -0,0 +1,409 @@
+package Module::PDFExtract;
+
+use strict;
+use warnings;
+use parent qw(Plack::Component);
+use Plack::Request;
+use File::Basename;
+use Data::Dumper;
+use PDF::API2; 
+use File::Path qw/make_path/;
+
+sub call {
+    my($self, $env) = @_;
+    #$self->_app->($env);
+    my $html->{result} = "unknown function";
+    if (($env->{REMOTE_ADDR} =~ "^127\.0\.") && 
+               ($env->{REMOTE_ADDR} =~ "^10\.") && 
+               ($env->{REMOTE_ADDR} =~ "^172\.16\.") && 
+               ($env->{REMOTE_ADDR} =~ "^192\.168\.")) {
+               return [
+       404,
+       [ 'Content-Type' => "text/html",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+               [ "Sorry no remote access allowed!" ]
+       ];
+       }
+       if ($env->{PATH_INFO} =~ /^\/pdfsplit/){
+               return $self->pdfsplit($env);
+       }
+       if ($env->{PATH_INFO} =~ /^\/pdfpagenumbers/){
+               return $self->pdfpagesnumbers($env);
+       }
+#      if ($env->{PATH_INFO} =~ /^\/pdfextract/){
+#              return $self->pdfextract($env);
+#      }
+       if ($env->{PATH_INFO} =~ /^\/parsedata/){
+               return $self->parsedata($env);
+       }
+       if ($env->{PATH_INFO} =~ /^\/parsestatement/){
+               return $self->parsestatement($env);
+       }
+       return [
+    404,
+     [ 'Content-Type' => 'text/html','Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+     [ "unknown function" ]
+  ];
+}
+
+sub pdfpagesnumbers(){
+       my $self = shift;
+       my $env = shift;
+       my $ct="application/json";
+       my $status=200;
+       my $req = Plack::Request->new($env);
+       my $html->{result}->{pagenumbers} = 0;
+       if (exists($req->query_parameters->{file}) && ($req->query_parameters->{file} =~ /\.pdf$/)){
+               my $pdf = PDF::API2->open($req->query_parameters->{file});
+               $html->{result}->{pagenumbers} = $pdf->pages;
+       }
+       return [
+    200,
+     [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+
+sub pdfsplit(){
+       my $self = shift;
+       my $env = shift;
+       my $html->{result} = ();
+       my $ct="application/json";
+       my $status=200;
+       my $req = Plack::Request->new($env);
+       my @nfiles = ();
+       my $outputdir = $ENV{TEMP};
+       if (exists($req->query_parameters->{file}) && exists($req->query_parameters->{prefix})){
+               my $basepdf = basename($req->query_parameters->{file});
+               $outputdir =~ s/\\/\//g;
+               
+               my $oldpdf = PDF::API2->open($req->query_parameters->{file});
+               my $xx = $oldpdf->pages;
+               for my $page_nb (1..$xx) {
+                       my $newpdf = PDF::API2->new;
+                       my $page = $newpdf->importpage($oldpdf, $page_nb);
+                       
+                       my $npdfname = $outputdir.'/'.$req->query_parameters->{prefix}.substr($basepdf,0,-4).".".$page_nb.".pdf"; 
+                       push @nfiles,$npdfname;
+                       if (-e $npdfname){  unlink($npdfname); }
+                       $newpdf->saveas($npdfname);
+               }
+       }
+       foreach my $n (@nfiles){
+               my $r  = $self->pdfextract($n);
+       }
+       $html->{result}->{files} = \@nfiles;
+       return [
+    200,
+     [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+};
+
+sub pdfextract(){
+       my $self = shift;
+       my $file = shift;
+#      my $html->{result} = ();
+#      my $ct="application/json";
+#      my $status=200;
+#      my $req = Plack::Request->new($env);
+       my $pdftotext;
+       my $sep = "/";
+       if ($^O eq "MSWin32") {
+               $sep = "\\";
+               $pdftotext=dirname($0).$sep.'pdftotext.exe';
+       }else {
+               $pdftotext=dirname($0).$sep.'pdftotext';
+       }
+       if (-e $file.'.txt'){
+       unlink($file.'.txt');
+    }
+       my $cmd = 'start /b "" "'.$pdftotext.'" -q -table -eol unix "'.$file.'" "'.$file.'.txt"';
+       my $st = `$cmd`;#'system(1,$cmd)' ;
+       #print $cmd."->".$st."\n";
+       return $st;
+}
+
+sub parsedata(){
+       my $self = shift;
+       my $env = shift;
+       my $req = Plack::Request->new($env);
+       if (exists($req->query_parameters->{type}) && exists($req->query_parameters->{file})){
+               if ($req->query_parameters->{type} eq "inv"){
+                       return $self->parseinvoice($req->query_parameters->{file});
+               } elsif ($req->query_parameters->{type} eq "invold"){
+                       return $self->parseoldinvoice($req->query_parameters->{file});
+               } elsif ($req->query_parameters->{type} eq "stmt") {
+                       return $self->parsestatement($req->query_parameters->{file});
+               } else {
+                       return [
+    404,
+     [ 'Content-Type' => 'text/html','Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+     [ "unknown function" ]
+  ];
+               }
+       }
+}
+
+sub parseinvoice(){
+  my $self = shift;
+  my $file = shift;
+  my $html->{result} = ();
+  my $ct="application/json";
+  my $status=200;
+  my $pxdata = ();
+  if (-e $file){
+       my @invoicedata = ();
+       open(EXT,$file);
+       while (my $l = <EXT>){
+               chomp($l);
+               push(@invoicedata,$l);
+       }
+       close(EXT);
+       foreach my $p (@invoicedata){
+        if ($p =~ /^N. Facture/) {
+          my ($tmp) = $p =~ m/.+\s(\d{4,}.\d{1,2}.\d{4,})\s.+$/;
+          $pxdata->{reference} = $tmp;
+        }
+        if ($p =~ /^Date de la facture/) {
+          my ($d,$m,$y) = $p =~ m/.+\s(\d{1,2}).(\d{1,2}).(\d{4,}).+$/;
+          if (length($d) == 1) { $d = "0".$d;}
+          if (length($m) == 1) { $m = "0".$m;}
+          $pxdata->{invoicedate} = $y.'-'.$m.'-'.$d;
+        }
+#        if (($p =~ /facture/) && ($pxdata->{invoicedate} eq "--")) {
+#          my ($d,$m,$y) = $p =~ m/.+\s(\d{1,2}).(\d{1,2}).(\d{4,})$/;
+#          if (length($d) == 1) { $d = "0".$d;}
+#          if (length($m) == 1) { $m = "0".$m;}
+#          $pxdata->{invoicedate} = $y.'-'.$m.'-'.$d;
+#        }
+        if ($p =~ /^Enfant/) {
+          my ($tmp) = $p =~ m/.+\s\((\d+)\).+$/;
+          $pxdata->{checkservice} = $tmp;
+        }
+        if (($p =~ /^\s+\(\d+\)$/) && (!defined($pxdata->{checkservice}))) {
+          my ($tmp) = $p =~ m/\s+\((\d+)\)$/;
+          $pxdata->{checkservice} = $tmp;
+        }
+        if ($p =~ /Heures.+\sh\s/) {
+          my ($hrs,$p1,$e1) = $p =~ m/.+Heures.+\s+([\s|\d]+,\d{1,2})\sh\s+([\s|\d]+,\d{1,2})\s+([\s|\d]+,\d{1,2}).+$/;
+          $p1 =~ s/,/\./;
+          $e1 =~ s/,/\./;
+          $p1 =~ s/\ //;
+          $e1 =~ s/\ //;
+          if (exists($pxdata->{hoursamount})){
+               $pxdata->{hoursamount} = $pxdata->{hoursamount} + $p1 + $e1;
+          } else {
+               $pxdata->{hoursamount} = $p1 + $e1;     
+          }
+          
+        }
+        if ($p =~ /Repas/) {
+          my ($rn,$p1,$e1) = $p =~ m/.+Repas.+\s+(\d+)\s+([\s|\d]+,\d{1,2})\s+([\s|\d]+,\d{1,2}).+$/;
+          $p1 =~ s/,/\./;
+          $e1 =~ s/,/\./;
+          $p1 =~ s/\ //;
+          $e1 =~ s/\ //;
+          $pxdata->{lunchnum} = $rn;
+          $pxdata->{lunchamount} = $p1 + $e1;
+        }
+        if ($p =~ /Participation totale de l.Etat/){
+               my ($e1) = $p =~ m/.+Participation totale de l.Etat\s+([\s|\d]+,\d{1,2}).+$/;
+               $e1 =~ s/,/\./;
+               $e1 =~ s/\ //;
+               $pxdata->{benefitamount} = $e1;
+        }
+        if ($p =~ /Montant\s.\sr.gler/) {
+          my ($m1) = $p =~ m/.+Montant.+\s+([\s|\d]+,\d{1,2}).+$/;
+          $m1 =~ s/,/\./;
+          $m1 =~ s/\ //;
+          $pxdata->{totalamount} = $m1;
+        }
+        #print Dumper(@pdata);
+      }
+  }
+  $html->{result} = $pxdata;
+  return [
+    200,
+     [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+
+sub parseoldinvoice(){
+  my $self = shift;
+  my $file = shift;
+  my $html->{result} = ();
+  my $ct="application/json";
+  my $status=200;
+  my $pxdata = ();
+  if (-e $file){
+       my @invoicedata = ();
+       open(EXT,$file);
+       while (my $l = <EXT>){
+               chomp($l);
+               push(@invoicedata,$l);
+       }
+       close(EXT);
+       foreach my $p (@invoicedata){
+        if ($p =~ /N. Facture/) {
+          my ($tmp) = $p =~ m/.+\s(\d{4,}.\d{1,2}.\d{4,})\s.+$/;
+          $pxdata->{reference} = $tmp;
+        }
+        if ($p =~ /Date de la/) {
+          my ($d,$m,$y) = $p =~ m/.+\s(\d{1,2}).(\d{1,2}).(\d{4,})$/;
+          if (length($d) == 1) { $d = "0".$d;}
+          if (length($m) == 1) { $m = "0".$m;}
+          $pxdata->{invoicedate} = $y.'-'.$m.'-'.$d;
+        }
+        if (($p =~ /facture/) && ($pxdata->{invoicedate} eq "--")) {
+          my ($d,$m,$y) = $p =~ m/.+\s(\d{1,2}).(\d{1,2}).(\d{4,})$/;
+          if (length($d) == 1) { $d = "0".$d;}
+          if (length($m) == 1) { $m = "0".$m;}
+          $pxdata->{invoicedate} = $y.'-'.$m.'-'.$d;
+        }
+        if ($p =~ /Carte N./) {
+          my ($tmp) = $p =~ m/.+\s(\d+)$/;
+          $pxdata->{checkservice} = $tmp;
+        }
+        if ($p =~ /Heure/) {
+          my ($tmp1,$tmp2) = $p =~ m/.+\s(\d+).(\d+)+$/;
+          $pxdata->{hoursamount} = $tmp1.'.'.$tmp2;
+        }
+        if ($p =~ /Repas/) {
+          my ($tmp1,$tmp2) = $p =~ m/.+\s(\d+).(\d+)+$/;
+          $pxdata->{lunchamount} = $tmp1.'.'.$tmp2;
+        }
+        if ($p =~ /Montant\s.\spayer/) {
+          my ($tmp1,$tmp2) = $p =~ m/.+\s(\d+).(\d+)+$/;
+          $pxdata->{totalamount} = $tmp1.'.'.$tmp2;
+        }
+        #print Dumper(@pdata);
+      }
+  }
+  $html->{result} = $pxdata;
+  return [
+    200,
+     [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+
+sub parsestatement(){
+       my $self = shift;
+       my $file = shift;
+       my $html->{result} = ();
+       my $ct="application/json";
+       my $status=200;
+       my $sxdata = ();
+       my $cmonth = "none";
+       my $frmonth = {"Janvier" => '01',"F�vrier"=> '02',"Mars" => '03',"Avril" => '04', "Mai" => '05',"Juin" => '06',"Juillet" => '07',"Ao�t" => '08',"Septembre" => '09',"Octobre" => '10',"Novembre" => '11',"D�cembre" => '12'}; 
+       if (-e $file){
+               my @xstmtdata = ();
+               open(EXT,$file);
+               while (my $l = <EXT>){
+                       chomp($l);
+                       push(@xstmtdata,$l);
+               }
+               close(EXT);
+               my $sxdata = ();
+  
+       foreach my $p (@xstmtdata){
+    if ($p =~ /P.riode/) {
+      my ($m1,$y1,$m2,$y2) = $p =~ m/.+\s(.+)\s+(\d+)\s+.\s+(.+)\s+(\d+)$/;
+      if (($m1 eq $m2) && ($y1 eq $y2)){
+        $cmonth=$y1.'-'.$frmonth->{$m1};
+      }
+    }
+    if ($p =~ /\d{13,}/) {
+      my ($csnum,$am) = $p =~ m/.+\s+(\d{13,})\s+([\d|\ |,]+)$/;
+      $am =~ s/\s+//;
+      $am =~ s/,/./;
+      $sxdata->{$cmonth}->{$csnum}=$am; 
+    }
+  }
+  }
+  $html->{result} = $sxdata;
+  return [
+    200,
+     [ 'Content-Type' => $ct,'Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+}
+
+
+#
+#sub importstatementdata(){
+#  my $simpdata = shift;
+#  my $fname = shift;
+#  #print Dumper($simpdata);
+#  my $n=0;
+#  foreach my $m (keys(%{$simpdata})){
+#    $n++;
+#     if (-e dirname($fname).$sep."prestation.".$m."-".$n.".pdf"){
+#      unlink(dirname($fname).$sep."prestation.".$m."-".$n.".pdf");
+#     }
+#     rename($fname,dirname($fname).$sep."prestation.".$m."-".$n.".pdf");
+#     foreach my $csnum (keys(%{$simpdata->{$m}})){
+#      $simpdata->{$m}->{fnum} = $n;
+#     }
+#  }
+#  foreach my $m (keys(%{$simpdata})){
+#    $n++;
+#    if ($m =~ /\d{4,}-\d{2,}/) {
+#      foreach my $csnum (keys(%{$simpdata->{$m}})){
+#        print "Import Check-Service no: " + $csnum + "\n";
+#        if (defined($db)){
+#          my $child = $db->dbquerysorted("select uuid from childs where replace(checkservicenumber,' ','') = '".$csnum."';");
+#          if (keys(%{$child}) == 1) {
+#            my $accdata = $db->dbquerysorted("select accmonth,childuuid from accounting where childuuid='".$child->{0}->{uuid}."' and accmonth=date('".substr($m,0,4).'-'.substr($m,5,2)."-01');");
+#            if (keys(%{$accdata}) == 1) {
+#              #make update
+#              my @upd = ();
+#              push @upd,"benefitamount='".$simpdata->{$m}->{$csnum}."'";
+#              push @upd,"benefitfile='prestation.".$m."-".$simpdata->{$m}->{fnum}.".pdf'";
+#              my $sql = "update accounting set ".join(',',@upd)." where childuuid='".$child->{0}->{uuid}."' and accmonth=date('".substr($m,0,4).'-'.substr($m,5,2)."-01');";
+#              #print $sql."\n";
+#              my $r = $db->dbexec($sql);
+#              if  (($log ne "") && (-e $log)){
+#                      if (!defined($r)) {
+#                      open(LOG,">>".$log);
+#                      print LOG localtime().":ERROR:".$sql."\n";
+#                      close(LOG);
+#                      } else {
+#                               open(LOG,">>".$log);
+#                      print LOG localtime().":SUCCESS:".$sql."\n";
+#                      close(LOG);
+#                      }
+#              }
+#            }else {
+#              my @ins1 = ();
+#              my @ins2 = ();
+#              push(@ins1,"accmonth");push (@ins2,"date('".substr($m,0,4).'-'.substr($m,5,2)."-01')");
+#              push(@ins1,"childuuid");push (@ins2,"'".$child->{0}->{uuid}."'");
+#              push(@ins1,"benefitamount");push (@ins2,"".$simpdata->{$m}->{$csnum}."");
+#              push(@ins1,"benefitfile");push (@ins2,"'prestation.".$m."-".$simpdata->{$m}->{fnum}.".pdf'");
+#              
+#              #accmonth,childuuid,invoicedate,invoiceamount,reference
+#              my $sql = "insert into accounting (".join(',',@ins1).") VALUES (".join(',',@ins2).");";
+#              #print $sql."\n";
+#              my $r = $db->dbexec($sql);
+#              if  (($log ne "") && (-e $log)){
+#                      if (!defined($r)) {
+#                      open(LOG,">>".$log);
+#                      print LOG localtime().":ERROR:".$sql."\n";
+#                      close(LOG);
+#                      } else {
+#                               open(LOG,">>".$log);
+#                      print LOG localtime().":SUCCESS:".$sql."\n";
+#                      close(LOG);
+#                      }
+#              }
+#            }
+#          }
+#        }
+#      }
+#    }
+#  }
+#}
+
+1;
\ No newline at end of file
diff --git a/server/Module/SQLite.pm b/server/Module/SQLite.pm
new file mode 100644 (file)
index 0000000..741743a
--- /dev/null
@@ -0,0 +1,255 @@
+package Module::SQLite;
+
+use strict;
+use warnings;
+use parent qw(Plack::Component);
+use Plack::Request;
+use File::Basename;
+#use Data::Dumper;
+use DBI;
+use DBD::SQLite;
+use Encode;
+use JSON::PP;
+
+sub call {
+    my($self, $env) = @_;
+    if (($env->{REMOTE_ADDR} =~ "^127\.0\.") && 
+               ($env->{REMOTE_ADDR} =~ "^10\.") && 
+               ($env->{REMOTE_ADDR} =~ "^172\.16\.") && 
+               ($env->{REMOTE_ADDR} =~ "^192\.168\.")) {
+               return [
+       404,
+       [ 'Content-Type' => "text/html",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+               [ "Sorry no remote access allowed!" ]
+       ];
+       }
+    #$self->_app->($env);
+    return $self->sqlite($env);
+}
+
+sub sqlite {
+       my $self = shift;
+       my $env = shift;
+       my $html->{result} = ();
+       my $ct="application/json";
+       my $status=200;
+       my $req = Plack::Request->new($env);
+       my $res = ();
+       #print $req->query_parameters->{db}.":".$req->query_parameters->{type}.":".decode_base64($req->query_parameters->{sql})."\n------------------\n";
+
+   if ($env->{PATH_INFO} =~ /^\/\w+/){
+      $self->{dbfile} = $self->{'dbpath'}.'/'.basename($env->{PATH_INFO}).'.sqlite';
+      $html->{req}->{db} = $self->{'dbpath'}.'/'.basename($env->{PATH_INFO}).'.sqlite';
+   }else {
+      if (exists($req->body_parameters->{db})){
+         $self->{dbfile} = $req->body_parameters->{db};
+         $html->{req}->{db} = $req->body_parameters->{db};
+      } else {
+         return [
+         400,
+         [ 'Content-Type' => $ct.'; charset=utf-8','Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+         [ JSON::PP::encode_json($html) ]
+       ];
+      }
+   }
+        
+       $html->{req}->{type} = $req->body_parameters->{type};
+       $html->{req}->{sql} = $req->body_parameters->{sql};
+       #$html->{req}->{sqldecoded} = $req->query_parameters->{sql}; 
+       if (exists($req->body_parameters->{sql}) && exists($req->body_parameters->{type})) {
+               #$self->{dbfile} = $req->query_parameters->{db};
+               #my $db = sqlite->new();
+               my $q = $req->body_parameters->{sql};
+               my $t = $req->body_parameters->{type};
+        print $q."\n";
+               if ($t eq "query"){
+                       $res = $self->dbquery($req->body_parameters->{key},$q);
+               } elsif ($t eq "querysorted"){
+                       $res = $self->dbquerysorted($q);
+               } elsif ($t eq "queryarray"){
+                       $res = $self->dbqueryarray($q);
+               } elsif ($t eq "exec"){
+                       $res = $self->dbexec($q);
+               }
+               $html->{result}->{sqldata} = $res;
+       } else {
+        return [
+         400,
+         [ 'Content-Type' => $ct.'; charset=utf-8','Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+         [ JSON::PP::encode_json($html) ]
+       ];
+     }
+       return [
+    200,
+     [ 'Content-Type' => $ct.'; charset=utf-8','Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+     [ JSON::PP::encode_json($html) ]
+  ];
+};
+
+
+
+sub strreplace(){
+    my $self = shift;
+    my $text = shift;
+    $text =~ s/'/''/g;
+    return $text;
+}
+
+sub dbquery(){
+    my $self = shift;
+    my $key = shift;
+    my $stat = shift;
+    my $retdata =();
+    my $dbh = DBI->connect('DBI:SQLite:dbname='.$self->{dbfile},"","",{PrintError=>1,RaiseError=>1,AutoCommit=>1})  or return $retdata->{error} = "dbquery Connection Error!".$!;
+    #$stat = encode("utf8", $stat);
+
+   #open FILE,">>/tmp/sql.log";
+   # print FILE "key:".$key.";$stat\n";
+   #  close FILE;
+    my $sth = $dbh->prepare($stat);
+   $sth->execute() or print "dbquery: ".$sth->errstr;
+   while(my $data = $sth->fetchrow_hashref())
+   {
+     if (exists $data->{$key}){
+        foreach my $k (keys %{$data}){
+            $retdata->{$data->{$key}}{$k} = decode( "utf8", $data->{$k});
+        }
+     }
+   }
+   if (keys(%{$retdata}) == 0){
+    $retdata =();
+   }
+   $sth->finish();
+   $dbh->disconnect();
+   return $retdata;
+}
+
+sub dbquerysorted(){
+    my $self = shift;
+    my $stat = shift;
+    my $retdata = ();
+     my $dbh = DBI->connect('DBI:SQLite:dbname='.$self->{dbfile},"","",{PrintError=>1,RaiseError=>1,AutoCommit=>1})  or return $retdata->{error} =  "dbquery Connection Error!".$!;
+    #$stat = encode("utf8", $stat);
+    #open FILE,">>/tmp/sql.log";
+    #print  "$stat\n";
+    # close FILE;
+    my $sth = $dbh->prepare($stat);
+
+   $sth->execute() or print "dbquery: ".$sth->errstr;
+   my $count = 0;
+   while(my $data = $sth->fetchrow_hashref())
+   {
+        foreach my $k (keys %{$data}){
+                $retdata->{$count}->{$k} = decode( "utf8", $data->{$k});
+        }
+     $count++;
+   }
+
+   $sth->finish();
+   $dbh->disconnect();
+   #%retdata = sort {$a <=> $b} keys %retdata;
+   return $retdata;
+}
+
+sub dbqueryarray(){
+    my $self = shift;
+    my $stat = shift;
+    my @retdata = ();
+     my $dbh = DBI->connect('DBI:SQLite:dbname='.$self->{dbfile},"","",{PrintError=>1,RaiseError=>1,AutoCommit=>1})  or return $retdata[0]->{error} =  "dbquery Connection Error!".$!;
+    #$stat = encode("utf8", $stat);
+    #open FILE,">>/tmp/sql.log";
+    #print  "$stat\n";
+    # close FILE;
+    my $sth = $dbh->prepare($stat);
+
+   $sth->execute() or print "dbquery: ".$sth->errstr;
+   my $count = 0;
+   
+   while(my $valdata = $sth->fetchrow_arrayref())
+   {
+               if (!defined($valdata)){ last;}
+           my @rdata = ();
+        foreach my $k (@{$valdata}){
+                push @rdata,decode( "utf8", $k);
+        }
+        push @retdata,\@rdata;
+   }
+
+   $sth->finish();
+   $dbh->disconnect();
+   #%retdata = sort {$a <=> $b} keys %retdata;
+   return \@retdata;
+}
+
+sub dbexec(){
+    my $self = shift;
+    my $stat = shift;
+    my $dbh = DBI->connect('DBI:SQLite:dbname='.$self->{dbfile},"","",{PrintError=>1,AutoCommit=>1})  or return  "dbexec Connection Error!".$!;
+    #$stat = encode("utf8", $stat);
+    #print $stat."\n";
+    #open FILE,">>/Users/kilian/sql.log";
+    #print FILE "$stat\n";
+    #close FILE;
+    my $sth = $dbh->prepare($stat);
+   my $rv =$dbh->do($stat) or print "Failed dbexec:\n'".$stat. "'\n\n";
+   $dbh->disconnect();
+   return $rv;
+}
+
+
+#sub dbbackup(){
+#    my $self = shift;
+#    my $path = shift;
+#    my $type = shift;
+#    
+#    my @dx = localtime();
+#    $dx[5] = $dx[5] +1900;
+#    $dx[4] = $dx[4] +1;
+#    if ($dx[4] < 10){$dx[4] = '0'.$dx[4];}
+#    if ($dx[3] < 10){$dx[3] = '0'.$dx[3];}
+#    if ($dx[2] < 10){$dx[2] = '0'.$dx[2];}
+#    if ($dx[1] < 10){$dx[1] = '0'.$dx[1];}
+#    if ($dx[0] < 10){$dx[0] = '0'.$dx[0];}
+#    my $xdd = $dx[5].$dx[4].$dx[3].'_'.$dx[2].$dx[1].$dx[0];
+#    my $bfile = "";
+#    if ($type eq "binary" ) {
+#        $bfile = $path.'/'.basename(substr($self->{dbfile},0,rindex($self->{dbfile},'.'))).'_'.$xdd.'.sqlite';
+#        my $dbh = DBI->connect('DBI:SQLite:dbname='.$self->{dbfile},"","",{PrintError=>1,RaiseError=>1,AutoCommit=>1})  or die "dbexec Connection Error!".$!;
+#        $dbh->sqlite_backup_to_file($bfile);
+#        $dbh->disconnect();
+#    }elsif($type eq "sql"){
+#        $bfile = $path.'/'.basename($self->{dbfile}).'_'.$xdd.'.sql';
+#        my $st = system('sqlite3 "'.$self->{dbfile}.'" ".dump" > '.$bfile);
+#    }
+#    return $bfile;
+#}
+#
+#sub dbrestore(){
+#    my $self = shift;
+#    my $file = shift;
+#    my $type = shift;
+#    if ($type eq "binary" ) {
+#        my $dbh = DBI->connect('DBI:SQLite:dbname='.$self->{dbfile},"","",{PrintError=>1,RaiseError=>1,AutoCommit=>1})  or die "dbexec Connection Error!".$!;
+#        $dbh->sqlite_backup_from_file($file);
+#        $dbh->disconnect();
+#    }elsif($type eq "sql"){
+#        open(REST,$file) or die "cannot open restore file $file!\n";
+#        my $rsql = "";
+#        while (my $l = <REST>) {
+#            $rsql .= $l;
+#        }
+#        close(REST);
+#        unlink($self->{dbfile});
+#        $self->dbexec($rsql);
+#    }
+#}
+#
+#sub dbrepair(){
+#    my $self = shift;
+#    my $bfile = $self->dbbackup($ENV{'TMPDIR'},'sql');
+#    $self->dbrestore($bfile,'sql');
+#    unlink($bfile);
+#}
+
+
+1;
\ No newline at end of file
diff --git a/server/Module/Service.pm b/server/Module/Service.pm
new file mode 100644 (file)
index 0000000..96caf93
--- /dev/null
@@ -0,0 +1,473 @@
+package Module::Service;
+
+use strict;
+use warnings;
+use File::Path qw(make_path);
+use File::Basename;
+use parent qw(Plack::Component);
+
+sub call {
+    my($self, $env) = @_;
+    if (($env->{REMOTE_ADDR} =~ "^127\.0\.") && 
+               ($env->{REMOTE_ADDR} =~ "^10\.") && 
+               ($env->{REMOTE_ADDR} =~ "^172\.16\.") && 
+               ($env->{REMOTE_ADDR} =~ "^192\.168\.")) {
+       return [
+    404,
+     [ 'Content-Type' => "text/html",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+     [ "Sorry no access allowed!" ]
+     ];
+  }
+    return $self->service($env);
+}
+
+sub service() {
+  my $self = shift;
+  my $env = shift;
+  my $html = "Unknown service!";
+  my $ct="application/json";
+  my $status=200;
+  
+  # if ($env->{PATH_INFO} =~ /^\/info/){
+  #    return $self->appinfo($env);
+  # }
+  # if ($env->{PATH_INFO} =~ /^\/preferences/){
+  #    return $self->preferences($env);
+  # }
+  if (($env->{PATH_INFO} =~ /^\/stop/) || ($env->{PATH_INFO} =~ /^\/unload/)){
+               warn "Killing the existing server (pid:".$self->{pid}.")\n";
+     kill 'TERM' => $self->{pid};
+
+    if (lc($^O) =~ /mswin32/) {
+        sleep 1;
+        kill 'KILL' => $self->{pid};
+    }
+    #  waitpid($self->{pid}, 0);
+    #  warn "Successfully killed! Restarting the new server process.\n";
+    
+  }
+  # if($env->{PATH_INFO} =~ /^\/getconfig/){
+  #    return $self->getconfig($env);
+  # }
+  
+  return [
+    200,
+     [ 'Content-Type' => 'text/html','Cache-Control' =>  'no-store, no-cache, must-revalidate' , 'Access-Control-Allow-Origin'=> '*'], 
+     [ $html ]
+  ];
+};
+
+# sub preferences(){
+#      my $self = shift;
+#      my $env =shift;
+#      my $name = basename($0);
+#      $name =~ s/\srv.pl$//;
+#      $name =~ s/\srv.exe$//;
+#      my $appcfgpath = $ENV{APPDATA}.'/'.$name;
+#      $appcfgpath =~ s/\\/\//g;
+#      my $pref->{result}= ();
+#      my $req = Plack::Request->new($env);
+#      if (exists($req->query_parameters->{page})){
+#              if (-e $appcfgpath.'/'.$req->query_parameters->{page}.'.json'){
+#                      open(PREF,$appcfgpath.'/'.$req->query_parameters->{page}.'.json');
+#                      my $strpref = "";
+#                      while (my $l = <PREF>){
+#                              $strpref .= $l;
+#                      }
+#                      close(PREF);
+#                      $pref->{result}=JSON::PP::decode_json($strpref); 
+#              }
+#              if (exists($req->query_parameters->{set})){
+#                      my $newpref = JSON::PP::decode_json($req->query_parameters->{set});
+#                      foreach my $p (keys(%{$newpref})){
+#                              $pref->{result}->{$p} = $newpref->{$p};
+#                      }
+#                      open(PREF,">".$appcfgpath.'/'.$req->query_parameters->{page}.'.json');
+#                      print PREF JSON::PP::encode_json($pref->{result});
+#                      close(PREF); 
+#              }
+#      }
+#      return [
+#     200,
+#      [ 'Content-Type' => "application/json",'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+#      [ JSON::PP::encode_json($pref) ]
+#   ]; 
+# }
+
+# sub appinfo(){
+#      my $self = shift;
+#      my $env = shift;
+#      my $html->{result} = ();
+#      my $req = Plack::Request->new($env);
+#      my $name = basename($0);
+#      $name =~ s/\.pl$//;
+#      $name =~ s/\.exe$//;
+#      $html->{result}->{OS} = $^O;
+#      $html->{result}->{app} = $name;
+#      if ($^O eq "MSWin32"){
+#              $html->{result}->{home} = $ENV{USERPROFILE};  
+#              $html->{result}->{user} = $ENV{USERNAME};  
+#              $html->{result}->{appcfgpath} = $ENV{APPDATA}.'/'.$name;
+#              $html->{result}->{hostname} = $ENV{COMPUTERNAME};
+#              $html->{result}->{arch} = $ENV{PROCESSOR_ARCHITEW6432};
+#              $html->{result}->{appcfgpath} =~ s/\\/\//g; 
+#              $html->{result}->{home}  =~ s/\\/\//g; 
+#      } else {
+#              $html->{result}->{home} = $ENV{HOME};  
+#              $html->{result}->{user} = $ENV{USER}; 
+#              if ($^O eq "darwin"){
+#                      $html->{result}->{appcfgpath} = $ENV{HOME}.'/Library/Application Support/'.$name;
+#              } else {
+#                      $html->{result}->{appcfgpath} = $ENV{HOME}.'/.'.$name.'/';
+#              }       
+#              $html->{result}->{hostname} = `hostname -s`;
+#              chomp($html->{result}->{hostname});
+#              $html->{result}->{arch} = `uname -m`;
+#              chomp($html->{result}->{arch});
+#      }
+#      if (! -e $html->{result}->{appcfgpath}){
+#                      make_path($html->{result}->{appcfgpath});
+#      }
+#      if (-e $html->{result}->{appcfgpath}.'/service.json'){
+#                      open(LCFG,$html->{result}->{appcfgpath}.'/service.json');
+#                      my $strljs = "";
+#                      while (my $l = <LCFG>){
+#                              $strljs .= $l;
+#                      }
+#                      close(LCFG);
+#                      print $strljs."\n--\n";
+#                      #if ($strljs =~ /^\{.*\}$/){
+#                              $html->{result}->{appconfig} = JSON::PP::decode_json($strljs);
+#                      #}
+#              }
+#      return [
+#     200,
+#      [ 'Content-Type' => "application/json",'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+#      [ JSON::PP::encode_json($html) ]
+#   ];
+# }
+
+# sub getfileconfig(){
+#      my $self = shift;
+#      my $strcfg = "";
+#      my $apppath = $self->getappconfigpath();
+#      if (-e $apppath.'/'.basename($apppath).'.conf'){
+#              open(AUTH,$apppath.'/'.basename($apppath).'.conf');
+#              while (my $l = <AUTH>){
+#                      chomp($l);
+#                      $strcfg .= $l;
+#              }
+#              close(AUTH);
+#      }
+#      if ($strcfg eq ""){
+#              return undef;
+#      } 
+#      return JSON::decode_json($strcfg);
+# }
+
+# sub getconfig(){
+#      my $self = shift;
+#      my $env = shift;
+#      my $html->{result} = undef;
+#      my $req = Plack::Request->new($env);
+#      my @sections = ('weblogin','cablenet','wirelessnet','openvpn','extdrives','shares','shareusers');
+#      if (exists($req->query_parameters->{section})){
+#              @sections = split(',',$req->query_parameters->{section});
+#      }
+#      foreach my $s (@sections){
+#              if ($s eq 'weblogin') {
+#                      $html->{result}->{$s} = $self->getweblogin();
+#              } elsif ($s eq 'cablenet') {
+#                      $html->{result}->{$s} = $self->getcablenetworkconfig();
+#              } elsif ($s eq 'wirelessnet') {
+                       
+#              } elsif ($s eq 'openvpn') {
+#                      $html->{result}->{$s} = $self->getOpenVPN();
+#              } elsif ($s eq 'extdrives') {
+#                      $html->{result}->{extdrives} = $self->getdrives();
+#              } elsif ($s eq 'shares') {
+#                      $html->{result}->{shares} = $self->getshares();
+#              } elsif ($s eq 'shareusers') {
+                       
+#              };
+#      }
+#      return [
+#     200,
+#      [ 'Content-Type' => "application/json",'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+#      [ JSON::PP::encode_json($html) ]
+#   ];
+# }
+
+# sub getweblogin(){
+#      my $self = shift;
+#      my $apppath = $self->getappconfigpath();
+#      my $loginname= "";
+#      if (-e $apppath.'/'.basename($apppath).'.passwd'){
+#              open(AUTH,$apppath.'/'.basename($apppath).'.passwd');
+#              while (my $l = <AUTH>){
+#                      chomp($l);
+#                      my ($loginname) = $l =~ /^(\w+)\=.*/;
+#              }
+#              close(AUTH);
+#      }
+#      return { "user" => $loginname }; 
+# }
+
+# sub setlogin(){
+#      my $self = shift;
+#      my $env = shift;
+#      my $req = Plack::Request->new($env);
+               
+# }
+
+# sub getwifinetworks(){
+#         my $self = shift;
+#         my $current = `sudo grep ssid /etc/wpa_supplicant/wpa_supplicant.conf`;
+#         chomp($current);
+#         my $wifi->{ssid} = undef;
+#         if ($current ne ""){
+#                 $wifi->{ssid} = $current;
+#         }
+#         my $strlist = `sudo iw wlan0 scan | grep SSID | sed \'s/.*SSID: //g\'`;
+#         my @list = split("\n",$strlist);
+#         $wifi->{networks} = \@list;
+#         return $wifi;
+# }
+
+
+
+
+#sub getusers(){
+#      my $self = shift;
+#      my $cmd = "";
+#      if ($^O eq "linux"){
+#              $cmd = 'cat /etc/passwd | grep "/bin/bash" | grep -v -e "^root" | awk -F ":" \'{ print $1 }\''; 
+#      } elsif ($^O eq "darwin"){
+#              $cmd = 'ls -1 /Users | grep -v "Shared"';
+#      } elsif ($^O eq "MSWin32"){
+#              
+#      } 
+#      my $strdata = `$cmd`;
+#      my @list = split("\n",$strdata);
+#      return \@list;  
+#}
+
+# sub getdrives(){
+#      my $self = shift;
+#      my $connecteddrives = $self->getconnecteddrives();
+#      my $mounts  = $self->getcurrentmountpoints();
+#      my $cfg = $self->getfileconfig();
+#      my $fstypes = { ntfs => 'ntfs-3g',exfat => 'exfat', fat32 => 'vfat'};
+#      my $drivesdata = ();
+#      foreach my $cd (keys(%{$connecteddrives})){
+#         if (exists($cfg->{drives}->{$connecteddrives->{$cd}->{serial}})){
+#                 $drivesdata->{$cd} = $connecteddrives->{$cd};
+#                 #print $cd." => ".$config->{drives}->{$connecteddrives->{$cd}->{serial}}->{path}."\n";
+# #                if (! -d $config->{drives}->{$connecteddrives->{$cd}->{serial}}->{path}){
+# #                        mkdir($config->{drives}->{$connecteddrives->{$cd}->{serial}}->{path});
+# #                }
+#                 if (exists($mounts->{$connecteddrives->{$cd}->{dev}})){
+#                      $drivesdata->{$cd}->{mounpoint} = $mounts->{$connecteddrives->{$cd}->{dev}};
+# #                        my $cmd = "sudo mount -t ".$fstypes->{$connecteddrives->{$cd}->{fs}}." -o uid=".$config->{drives}->{$connecteddrives->{$cd}->{serial}}->{uid}.',gid='.$config->{drives}->{$connecteddrives->{$cd}->{serial}}->{gid}.','.$config->{drives}->{$connecteddrives->{$cd}->{serial}}->{type}." /dev/".$connecteddrives->{$cd}->{dev}." ".$config->{drives}->{$connecteddrives->{$cd}->{serial}}->{path};
+#                         #print $cmd."\n";
+# #                        my $res = `$cmd`;
+#                 }
+#         }
+#      }
+#      return $drivesdata;
+# }
+
+# sub mountdrives(){
+#      my $self = shift;
+#      #mount -t ntfs-3g -o uid=1001,gid=1001,rw /dev/sdb1 /home/dks/music
+# }
+
+# sub getshares(){
+#      my $self = shift;
+#      my $shares =();
+#      open(SMB,'/etc/samba/smb.conf');
+#      my $cgrp = "";
+#      while (my $l = <SMB>){
+#              chomp($l);
+#              if ($l =~ /^\[.*\]$/){
+#                      $cgrp = $l;
+#                      $cgrp =~ s /\[//;
+#                      $cgrp =~ s /\]//;
+#              } elsif ($l ne ""){
+#                      my ($k,$v) = $l =~ m/\s*(.+)\s+\=\s+(.*)$/; 
+#                      if ($cgrp eq "global"){
+#                              if (($k eq "workgroup") || ($k eq "server string") || ($k eq "netbios name")){
+#                                      $shares->{$cgrp}->{$k} =$v;
+#                              }       
+#                      }else {
+#                              if (($k eq "comment") || ($k eq "path") || ($k eq "vialid users")){
+#                                      $shares->{$cgrp}->{$k} =$v;
+#                              }
+#                      }
+#              }
+#      }
+#      close(SMB);
+#      return $shares;
+# }
+
+# sub setshares(){
+#      my $self = shift;
+# }
+
+
+# sub getwifinetworks(){
+#      my $self = shift;
+#      #iw wlan0 scan | grep SSID | sed 's/.*SSID: //g'
+# # }
+
+# sub setwifinetwork(){
+#      my $self = shift;
+#      #/etc/wpa_supplicant/wpa_supplicant.conf
+#      #network={
+#     #ssid="testing"
+#     #psk="testingPassword"
+#     ##encrypted:
+#     #psk=131e1e221f6e06e3911a2d11ff2fac9182665c004de85300f9cac208a6a80531
+#     #wpa_passphrase "testing" "testingPassword"
+#      #}
+#      #wpa_cli -i wlan0 reconfigure
+#      ##unsecurd
+# #    network={
+# #    ssid="testing"
+# #    key_mgmt=NONE
+# #}
+# ##HIDDEN
+# #    network={
+# #    ssid="yourHiddenSSID"
+# #    scan_ssid=1
+# #    psk="Your_wifi_password"
+# #}   
+# ##MULTIPLE with option apriority
+# #network={
+# #    ssid="HomeOneSSID"
+# #    psk="passwordOne"
+# #    priority=1
+# #    id_str="homeOne"
+# #}
+# #
+# #network={
+# #    ssid="HomeTwoSSID"
+# #    psk="passwordTwo"
+# #    priority=2
+# #    id_str="homeTwo"
+# #}   
+# }
+
+# sub configureOpenVPN(){
+#      my $self = shift;
+#      my $file = shift;
+# }
+
+# sub getOpenVPN(){
+#      my $self = shift;
+#      opendir(OVPN,'/etc/openvpn');
+#      my $ovpndata = ();
+#      print "read OPENVPN:\n";
+#      while(my $f = readdir(OVPN)){
+#              print "OVPN file:".$f."\n";
+#              if (($f =~ /\.ovpn$/) || ($f =~ /\.conf$/)){
+#                      my $name = substr($f,0,-6);
+#                      $ovpndata->{$name} = undef;
+#              }
+#      }
+       
+#      closedir(OVPN);
+#      return $ovpndata;
+# }
+
+# sub getOpenVPNstatus(){
+       
+# }
+
+# sub getcablenetworkconfig(){
+#      my $self = shift;
+#      my $cmd = "";
+#      my $ncfg->{type} = "dhcp";
+#      if ($^O eq "linux"){
+#              $cmd = 'cat /etc/dhcpcd.conf | grep -e "^static\|^interface"';
+#              my $strdata = `$cmd`;
+#              my @data = split("\n",$strdata);
+#              foreach my $d (@data){
+#                      if ($d =~  /static\sip_address/){
+#                              $ncfg->{ip} = $d =~ /.+=(.+)\/\d{1,2}$/;
+#                              $ncfg->{subnet} = $d =~ /.+=.+\/(\d{1,2})$/;
+#                              #substr(Net::CIDR::addrandmask2cidr($stdata->{0}->{networkip}, $stdata->{0}->{networksubnet}),-3)
+#                      }
+#                      if ($d =~  /static\srouters/){
+#                              $ncfg->{gateway} = $d =~ /.+=(.+)$/;
+#                      }
+#                      if ($d =~  /static\sdomain_name_servers/){
+#                              $ncfg->{dns} = $d =~ /.+=(.+)$/;
+#                      }
+#              }
+#      } elsif ($^O eq "darwin"){
+#              $cmd = '';
+#      } elsif ($^O eq "MSWin32"){
+#              $cmd = '';
+#      }
+#      return $ncfg;
+# }
+
+# sub getappconfigpath(){
+#      my $self = shift;
+#      my $name = basename($0);
+#      $name =~ s/\.pl$//;
+#      $name =~ s/\.exe$//;
+#      my $path = "";
+#      if ($^O eq "MSWin32"){
+#              $path = $ENV{APPDATA}.'/'.$name;
+#      } elsif ($^O eq "darwin"){
+#              $path = $ENV{HOME}.'/Library/Application Support/'.$name;
+#      } else {
+#              $path = $ENV{HOME}.'/.'.$name.'/';
+#      }
+#      return $path;   
+# }
+
+# sub getconnecteddrives(){
+#      my $self = shift;
+#         my $cmd = 'lsblk -o name,label,size,mountpoint,fstype,SERIAL | grep -e "sd."';
+#         my $strdrives = `$cmd`;
+#         my $drives = ();
+#         my @drives = split("\n",$strdrives);
+#         foreach my $l (@drives){
+#          $l =~ tr/a-zA-Z0-9 ._-//cd;
+#          $l =~ s/\s+/\ /g;
+#          my @tmp = split(" ",$l);
+#          #print Dumper(@tmp);
+#          if ( $l =~ /^sd.\d/ ) {
+#                 my $drv = substr($tmp[0],0,3);
+#                 $drives->{$drv}->{dev} = $tmp[0];
+#                 $drives->{$drv}->{label} = $tmp[1];
+#                 $drives->{$drv}->{size} = $tmp[2];
+#                 $drives->{$drv}->{fs} = $tmp[3];
+#          } else {
+#                 #print $l."\n";
+#                 $drives->{$tmp[0]}->{serial} = $tmp[2];
+#          }
+#         }
+
+#         return $drives;
+# }
+
+# sub getcurrentmountpoints(){
+#      my $self = shift;
+#         my $cmd = 'mount | grep "/dev/sd"';
+#         my $mpoints = ();
+#         my $strmounts = `$cmd`;
+#         #print $strmounts;
+#         my @mdrives = split("\n",$strmounts);
+#         foreach my $m (@mdrives){
+#                 my ($drv,$path) = $m =~ m/^\/dev\/(.+)\son\s(.*)\stype.*$/;
+#                 $mpoints->{$drv} = $path;
+#         }
+#         return $mpoints;
+# }
+
+
+1;
\ No newline at end of file
diff --git a/server/Module/Test.pm b/server/Module/Test.pm
new file mode 100644 (file)
index 0000000..acb92c6
--- /dev/null
@@ -0,0 +1,54 @@
+package Module::Test;
+
+use strict;
+use warnings;
+use parent qw(Plack::Component);
+use Plack::Request;
+use Data::Dumper;
+use URI::Encode qw/uri_encode/;
+
+sub call {
+    my($self, $env) = @_;
+    #$self->_app->($env);
+    if (($env->{REMOTE_ADDR} =~ "^127\.0\.") && 
+               ($env->{REMOTE_ADDR} =~ "^10\.") && 
+               ($env->{REMOTE_ADDR} =~ "^172\.16\.") && 
+               ($env->{REMOTE_ADDR} =~ "^192\.168\.")) {
+               return [
+       404,
+       [ 'Content-Type' => "text/html",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+               [ "Sorry no remote access allowed!" ]
+       ];
+       }
+    return $self->test($env);
+}
+
+sub test(){
+       my $self = shift;
+       my $env = shift;
+       my $html = {};
+       my $ct="application/json";
+       my $status=200;
+       my $request = Plack::Request->new($env);
+       
+       foreach my $k (keys(%ENV)){
+               $html->{sysenv}->{$k} = $ENV{$k};
+       }
+       
+       foreach my $k (keys(%{$env})){
+               $html->{request}->{$k} = uri_encode($env->{$k});
+       }
+       # $html .= "<h1>GET PARAMETERS</h1>";
+       # $html .= Dumper($request->query_parameters);
+       # $html .= "<h1>POST PARAMETERS</h1>";
+       # $html .= Dumper($request->body_parameters);
+       # print "Test Called!\n";
+       return [
+    200,
+     [ 'Content-Type' => "application/json",'Cache-Control' =>  'no-store, no-cache, must-revalidate', 'Access-Control-Allow-Origin'=> '*' ], 
+     [ JSON::PP::encode_json($html) ]
+       ];
+};
+
+
+1;
\ No newline at end of file
diff --git a/server/Module/VPNServer.pm b/server/Module/VPNServer.pm
new file mode 100644 (file)
index 0000000..ec6144d
--- /dev/null
@@ -0,0 +1,69 @@
+package Module::Test;
+
+use strict;
+use warnings;
+use parent qw(Plack::Component);
+use Plack::Request;
+use Data::Dumper;
+
+sub call {
+    my($self, $env) = @_;
+    #$self->_app->($env);
+    if (!exists($env->{REMOTE_USER}) ) {
+               return [
+       404,
+       [ 'Content-Type' => "text/html",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+               [ "Access not allowed!" ]
+       ];
+       }
+       if ($env->{PATH_INFO} =~ /^\/server/){
+               return $self->server($env);     
+       } elsif ($env->{PATH_INFO} =~ /^\/client/) {
+               return $self->client($env);
+       } else {
+               return $self->clientslist($env)
+       }
+    return return [
+       404,
+       [ 'Content-Type' => "text/html",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+               [ "Access not allowed!" ]
+       ];;
+}
+
+sub server(){
+       my $self = shift;
+       my $env = shift;
+       my $html = "";
+       my $ct="application/json";
+       my $status=200;
+       my $request = Plack::Request->new($env);
+       if ($env->{PATH_INFO} =~ /^\/server\/list/){
+               
+       }
+       return [
+    200,
+     [ 'Content-Type' => "application/json",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+     [ $html ]
+  ];
+};
+
+sub client(){
+       my $self = shift;
+       my $env = shift;
+       my $html = "";
+       my $ct="application/json";
+       my $status=200;
+       my $request = Plack::Request->new($env);
+       if ($env->{PATH_INFO} =~ /^\/client\/add/){
+               
+       } elsif ($env->{PATH_INFO} =~ /^\/client\/revoke/){
+               
+       }
+       return [
+    200,
+     [ 'Content-Type' => "application/json",'Cache-Control' =>  'no-store, no-cache, must-revalidate' ], 
+     [ $html ]
+  ];
+};
+
+1;
\ No newline at end of file
diff --git a/server/createpdfA4invoice.pl b/server/createpdfA4invoice.pl
new file mode 100644 (file)
index 0000000..fe41671
--- /dev/null
@@ -0,0 +1,224 @@
+#!/usr/bin/env perl
+
+use strict;
+use PDF::API2;
+use PDF::Table;
+use Image::Size;
+use File::Basename;
+use Getopt::Long;
+use utf8;
+use Encode;
+use JSON::PP;
+#use Data::Dumper;
+my $pdf = PDF::API2->new();
+my $strpdfdata ="";
+my $templatedata =();
+my $data = "";
+my $pdfout = "";
+my $lang = "fr";
+# my $datafile=dirname($0).'/pdftest.pdf';
+GetOptions ("data|d=s" => \$data, "pdfoutput|o=s" => \$pdfout, "lang|l=s" =>\$lang);
+if (! -e $datafile){
+  print "file $datafile does not exist!\n";
+  exit(1);
+}
+if (-e $pdfout){
+  unlink($pdfout);
+}
+
+
+
+my $strtemplatedata = '{
+  "section":{
+    "12headerimg":{"x":50,"y":755,"width":160,"type":"image"},
+    "02headerline":{"type":"line","color":"black","width":1,"start":{"x":50,"y":753},"end":{"x":285,"y":753}},
+    "03dkstext":{"type":"text","align":"center","font":{"name":"Helvetica","size":9},"x":200,"y":743},
+    "04dksfooter":{"type":"text","align":"center","font":{"name":"Helvetica","size":10},"x":297.5,"y":25,"lineheight":15},
+    "05adrline":{"type":"line","color":"black","width":1,"start":{"x":52,"y":662},"end":{"x":280,"y":662}},
+    "06header_right":{"type":"text","align":"right","font":{"name":"Helvetica","size":11},"x":550,"y":820,"lineheight":14},
+    "07reporttype":{"type":"text","align":"right","font":{"name":"HelveticaBold","size":30},"x":550,"y":704,"lineheight":14},
+    "08tbladdress":{"type":"table","x":"50","w":"230","font":"Helvetica","font_size":"11","start_y":"680","start_h":"620","next_y":"750","next_h":"500","padding":"2","padding_bottom":"3","padding_top":"3","padding_right":"5","column_props":[{ "min_w":"200", "max_w":"200", "justify":"left" }],"header_props":[{ "font_size":"11", "font":"HelveticaBold", "bg_color":"white", "font_color":"black", "repeat":"1", "justify":"left" }],"border":"0"},
+    "09tblinvoicedata":{"type":"table","x":"390","w":"175","font":"Helvetica","font_size":"11","start_y":"680","start_h":"620","next_y":"750","next_h":"500","padding":"2","padding_bottom":"3","padding_top":"3","padding_right":"5","column_props":[ { "min_w":"70","max_w":"70","justify":"left" },{ "min_w":"90","max_w":"90","justify":"left","font":"HelveticaBold" } ],"border":"0"},
+    "10tblproductdata":{"type":"table","x":"50","w":"500","font":"Helvetica","font_size":"11","start_y":"570","start_h":"300","next_y":"750","next_h":"500","padding":"2","padding_left":"5","padding_bottom":"10","padding_top":"5","padding_right":"5","header_props":[{"font_size":"11","font":"HelveticaBold","bg_color":"#e6e6e6","font_color":"black","repeat":"1","justify":"center" }],"column_props":[ { },{ "min_w":"40","max_w":"40","justify":"right" },{ "min_w":"20","max_w":"20","justify":"right" },{ "min_w":"50","max_w":"50","justify":"right","font":"Helvetica" } ],"border":"0.5"},
+    "11totalsums":{"type":"table","x":"349.5","w":"201","font":"Helvetica","font_size":"11","start_y":"%%10tblproductdata:y%%","start_h":"150","next_y":"750","next_h":"500","padding":"2","padding_left":"5","padding_bottom":"10","padding_top":"5","padding_right":"5","column_props":[ { "justify":"right" },{ "min_w":"70","max_w":"70","justify":"right","font":"HelveticaBold" } ],"border":"0.5"}
+  },
+  "data":{"fr":{
+    "12headerimg":{"src":"'.dirname($0).'/dks_1000.png"},
+    "03dkstext":{"text":["Database Knowledge Solutions - Simplify IT!"]},
+    "04dksfooter":{"text":["DKS, Société à responsabilité limitée, RC Luxembourg,B168572 - TVA: LU 2537 5617 - No. Aut: 10024550 / 0","IBAN: LU25 0020 1100 2783 8700; BIC: BILLLULL"]},
+    "06header_right":{"text":["DKS s.à r.l.","8b, rue du Moulin","6914 Roodt/Syre","Tel: +352 691 504574","info@dks.lu / www.dks.lu"]},
+    "08tbladdress":{"text":[ ["À:"], ["%%RECEPIENT%%"], ["%%ADDRESS"], ["%%COUNTYSHORT%%-%%ZIP%% %%CITY%%"] ]},
+    "07reporttype":{"text":["Facture"]},
+    "09tblinvoicedata":{"text":[ ["NO. Facture","%%REFERENCE%%"], ["Date","%%INVOICEDATE%%"], ["Échéanche","%%REMINDERDATE%%"], ["No Client","%%CLIENTNUMBER%%"], ["Ust-Id","%%VATID%%"] ]},
+    "10tblproductdata":{"text":[ ["Produit", "Qu.","Prix unitaire","Prix Net"], ["%%PRODUCT%%", "%%QUANTIY%% %%UNIT%%","%%UNITAMOUNT%% %%CURRENCY%%","%%NETAMOUNT%% %%CURRENCY%%"] ]},
+    "11totalsums":{"text":[ ["Total Net :","%%SUMNETAMOUNT%% %%CURRENCY%%"], ["TVA (%%VATPERCENT%%%) :","%%SUMVATAMOUNT%% %%CURRENCY%%"], ["Total à payer:","%%SUMGROSSAMOUNT%% %%CURRENCY%%"] ]}
+  },"de":{
+    "12headerimg":{"src":"'.dirname($0).'/dks_1000.png"},
+    "03dkstext":{"text":["Database Knowledge Solutions - Simplify IT!"]},
+    "04dksfooter":{"text":["DKS, Société à responsabilité limitée, RC Luxembourg,B168572 - TVA: LU 2537 5617 - No. Aut: 10024550 / 0","IBAN: LU25 0020 1100 2783 8700; BIC: BILLLULL"]},
+    "06header_right":{"text":["DKS s.à r.l.","8b, rue du Moulin","6914 Roodt/Syre","Tel: +352 691 504574","info@dks.lu / www.dks.lu"]},
+    "08tbladdress":{"text":[ ["An:"], ["%%RECEPIENT%%"], ["%%ADDRESS"], ["%%COUNTYSHORT%%-%%ZIP%% %%CITY%%"] ]},
+    "07reporttype":{"text":["Rechnung"]},
+    "09tblinvoicedata":{"text":[ ["Rechnungs-Nr.","%%REFERENCE%%"], ["Datum","%%INVOICEDATE%%"], ["Fälligkeit","%%REMINDERDATE%%"], ["Kunden-Nr.","%%CLIENTNUMBER%%"], ["Ust-Id","%%VATID%%"] ]},
+    "10tblproductdata":{"text":[ ["Produkt", "Anzahl","EinzelPreis","Netto-Preis"], ["%%PRODUCT%%", "%%QUANTIY%% %%UNIT%%","%%UNITAMOUNT%% %%CURRENCY%%","%%NETAMOUNT%% %%CURRENCY%%"] ]},
+    "11totalsums":{"text":[ ["Gesamt Netto :","%%SUMNETAMOUNT%% %%CURRENCY%%"], ["MwSt. (%%VATPERCENT%%%) :","%%SUMVATAMOUNT%% %%CURRENCY%%€"], ["Gesamt Brutto :","%%SUMGROSSAMOUNT%% %%CURRENCY%%"] ]}
+  },"en":{
+    "12headerimg":{"src":"'.dirname($0).'/dks_1000.png"},
+    "03dkstext":{"text":["Database Knowledge Solutions - Simplify IT!"]},
+    "04dksfooter":{"text":["DKS, Société à responsabilité limitée, RC Luxembourg,B168572 - TVA: LU 2537 5617 - No. Aut: 10024550 / 0","IBAN: LU25 0020 1100 2783 8700; BIC: BILLLULL"]},
+    "06header_right":{"text":["DKS s.à r.l.","8b, rue du Moulin","6914 Roodt/Syre","Tel: +352 691 504574","info@dks.lu / www.dks.lu"]},
+    "08tbladdress":{"text":[ ["To:"],["%%RECEPIENT%%"], ["%%ADDRESS"], ["%%COUNTYSHORT%%-%%ZIP%% %%CITY%%"] ]},
+    "07reporttype":{"text":["Invoice"]},
+    "09tblinvoicedata":{"text":[ ["Reference.","%%REFERENCE%%"], ["Date","%%INVOICEDATE%%"], ["Due Date","%%REMINDERDATE%"], ["Client-Id","%%CLIENTNUMBER%%"], ["VAT-Id","%%VATID%%"] ]},
+    "10tblproductdata":{"text":[ ["Product", "Quantity","Unit Amount","Net Amount"], ["%%PRODUCT%%", "%%QUANTIY%% %%UNIT%%","%%UNITAMOUNT%% %%CURRENCY%%","%%NETAMOUNT%% %%CURRENCY%%"] ]},
+    "11totalsums":{"text":[ ["Net Total :","%%SUMNETAMOUNT%% %%CURRENCY%%"], ["VAT (%%VATPERCENT%%%) :","%%SUMVATAMOUNT%% %%CURRENCY%%€"], ["Total to Pay:","%%SUMGROSSAMOUNT%% %%CURRENCY%%"] ]
+  },
+  }
+}';
+
+
+open(DATA,$strtemplatedata);
+while (my $l = <DATA>){
+  $strpdfdata .= $l;
+}
+close(DATA);
+$templatedata = JSON::PP::decode_json($strpdfdata);
+my $pdfdata->{section} = $templatedata->{section};
+$pdfdata->{data} = $templatedata->{data}->{$lang};
+
+$pdf->preferences({-fitwindow => 1});
+
+
+my $page = $pdf->page();
+$page->mediabox('A4');
+
+
+
+
+my $endpoints = ();
+foreach my $s (sort keys(%{$pdfdata->{section}})){
+  $endpoints->{$s} = {final_y => '', lastpage => 1,pages => 1};
+  if ($pdfdata->{section}->{$s}->{type} eq "image"){
+    #print "Add Image $s\n";
+    
+    $endpoints->{$s}->{y} = &addimage($pdf,$page,$pdfdata->{section}->{$s},$pdfdata->{data}->{$s});
+  } elsif ($pdfdata->{section}->{$s}->{type} eq "line"){
+    #print "Add Line $s\n";
+    $endpoints->{$s}->{y} = &addline($page,$pdfdata->{section}->{$s});
+  } elsif ($pdfdata->{section}->{$s}->{type} eq "text"){
+    #print "Add Text $s\n";
+    $endpoints->{$s}->{y} = &addtext($pdf,$page,$pdfdata->{section}->{$s},$pdfdata->{data}->{$s});
+  } elsif ($pdfdata->{section}->{$s}->{type} eq "table"){
+    #print "Add Table $s\n";
+    ($endpoints->{$s}->{lastpage}, $endpoints->{$s}->{pages}, $endpoints->{$s}->{y}) = &addtable($pdf,$page,$pdfdata->{section}->{$s},$pdfdata->{data}->{$s});
+  }
+}
+
+$pdf->saveas($pdfout);
+$pdf->end;
+
+#Functions
+
+sub addimage(){
+  my $pdf = shift;
+  my $page = shift;
+  my $section = shift;
+  my $data = shift;
+  my $gfx = $page->gfx();
+  my ($iw, $ih) = imgsize($data->{src});
+  my $nimgw = $section->{width};
+  my $nimgh = ($nimgw/$iw) * $ih;
+  #print "New Imagesize: w:".$nimgw." h:".$nimgh."\n";
+  $gfx->translate($section->{x},$section->{y});
+  my $img = $pdf->image_png($data->{src});
+  $gfx->image($img,0,0,$nimgw,$nimgh);
+  $gfx->translate(0,0);
+  $gfx->save;
+  return $nimgh + $section->{y};
+}
+
+sub addline(){
+  my $page = shift;
+  my $section = shift;
+  my $line = $page->gfx();
+  $line->translate($section->{x},$section->{y});
+  $line->strokecolor($section->{color});
+  $line->linewidth($section->{width});
+  $line->move( $section->{start}->{x}, $section->{start}->{y} );
+  $line->line( $section->{end}->{x}, $section->{end}->{y} );
+  $line->stroke;
+  $line->save;
+  return $section->{end}->{y};
+}
+
+sub addtext(){
+  my $pdf = shift;
+  my $page = shift;
+  my $section = shift;
+  my $data = shift;
+  
+  my $textobj = $page->text();
+  
+  #print "X:".$section->{x}." Y:".$section->{y}."\n";
+  $textobj->translate($section->{x}, $section->{y});
+  $textobj->font($pdf->corefont($section->{font}->{name}),$section->{font}->{size});
+  foreach my $d (@{$data->{text}}){
+    if ($section->{align} eq "center"){
+      $textobj->text_center($d);
+    } elsif ($section->{align} eq "right"){
+      $textobj->text_right($d);
+    } else {
+      $textobj->text($d);
+    }
+    $section->{y} = $section->{y} - $section->{lineheight};
+    $textobj->translate($section->{x}, $section->{y});
+  }
+  $textobj->save;
+  return $section->{y};
+}
+
+sub addtable(){
+  my $pdf = shift;
+  my $page = shift;
+  my $section = shift;
+  my $data = shift;
+  my $pdftbl = new PDF::Table;
+  delete($section->{type});
+  if (exists($section->{font})){
+    $section->{font} = $pdf->corefont($section->{font});
+  }
+  if (exists($section->{start_y})){
+    if ($section->{start_y} =~ /^%%.*%%$/){
+      my ($sec,$k) = $section->{start_y} =~ /^%%(.+):(.+)%%$/;
+      $section->{start_y} = $endpoints->{$sec}->{$k};
+    }
+  }
+  #print "HeadProps\n";
+  if (exists($section->{header_props})){
+    foreach (my $i=0;$i<scalar(@{$section->{header_props}});$i++){
+      my $tmpdata = @{$section->{header_props}}[$i];
+      if (exists($tmpdata->{font})){
+        $tmpdata->{font} = $pdf->corefont($tmpdata->{font});
+      }
+      @{$section->{header_props}}[$i] = $tmpdata;
+    } 
+  }
+  #print "ColProps\n";
+  if (exists($section->{column_props})){
+    foreach (my $i=0;$i<scalar(@{$section->{column_props}});$i++){
+      my $tmpdata = @{$section->{column_props}}[$i];
+      if (exists($tmpdata->{font})){
+        $tmpdata->{font} = $pdf->corefont($tmpdata->{font});
+      }
+      @{$section->{column_props}}[$i] = $tmpdata;
+    } 
+  }
+  #print Dumper($section);
+  #print ref %{$section}."\n";
+  my ($lastpage, $tblpages, $final_y) = $pdftbl->table($pdf,$page, $data->{text}, %{$section});
+  return ($lastpage, $tblpages, $final_y);
+}
+
+
+
+
diff --git a/server/fmtosqlite.pl b/server/fmtosqlite.pl
new file mode 100644 (file)
index 0000000..cf8f000
--- /dev/null
@@ -0,0 +1,110 @@
+#!/usr/bin/env perl
+
+use strict; 
+use Getopt::Long;
+use File::Basename;
+use Data::Dumper;
+use Encode;
+use Getopt::Long;
+my $file = "";
+my $outfile = ""; 
+#my $dbfile = "";
+GetOptions("infile|i=s" => \$file, "outfile|o=s" => \$outfile); 
+
+my $fdata = "";
+open (FF,$file);
+while (my $l = <FF>){
+    #chomp($l);
+    $l =~ s/\r//g;
+    $l =~ s/\n//g;
+    #print $l."\n\n";
+    $fdata .= $l;
+}
+close(FF);
+#print $fdata;
+$fdata =~ s/<TD><BR><\/TD>/<TD><\/TD>/g;
+my ($tbl) = $fdata =~ m/.*<TABLE BORDER=1>(.*)<\/TABLE>.*/;
+$tbl =~ s/^<TR>//;
+$tbl =~ s/<\/TR>$//;
+
+my @strdata = split('</TR><TR>',$tbl);
+my $strcol = shift(@strdata);
+#print Dumper(@strdata);
+#print Dumper($strcol);
+#get cols;
+my $tablename = substr(basename($file),0,rindex(basename($file),"."));
+
+my @cols = &getheaderarray($strcol); 
+my @rows = ();
+foreach my $d (@strdata){
+    my @rdata = &getdataarray($d);
+    push (@rows,\@rdata);    
+}
+print Dumper(@rows);
+#print Dumper(@cols); 
+open (FOUT,">".$outfile);
+print FOUT &tableddl($tablename,\@cols);
+
+foreach my $r (@rows){
+    my @nr = ();
+    foreach my $d (@{$r}){
+        if ($d eq ""){$d = 'null';} 
+        elsif ($d =~ /^\d\d\/\d\d\/\d\d\d\d\s+\d\d:\d\d:\d\d$/){
+            my ($c,$m,$y,$h,$n,$s) = $d =~ m/(\d\d)\/(\d\d)\/(\d\d\d\d)\s+(\d\d):(\d\d):(\d\d)/;
+            $d = "datetime('".$y.'-'.$m.'-'.$c.' '.$h.':'.$n.':'.$s."')";
+        }elsif ($d =~ /^\d+,\d+$/){
+            $d =~ s/,/./;
+        }else {
+            $d =~ s/\r$//;
+            $d =~ s/\n$//;
+            $d = "'".encode('utf-8',$d)."'";
+        }
+        push (@nr,$d);
+    }
+    my $sql = "INSERT INTO ".$tablename." (".join(',',@cols).") VALUES (".join(",",@nr).");";
+    print $sql."\n";
+    print FOUT $sql."\n";
+}
+close(FOUT);
+
+
+sub getheaderarray(){
+    my $strdata = shift;
+    $strdata =~ s/^<TH>//;
+    $strdata =~ s/<\/TH>$//;
+    $strdata = lc($strdata);
+    $strdata =~ s/\s+/\ /g;
+    $strdata =~ s/\s/\_/g;
+    my @data = split('</th><th>',$strdata);
+    return @data;
+}
+
+sub getdataarray(){
+    my $strdata = shift;
+
+    $strdata =~ s/^<TD>//;
+    $strdata =~ s/<\/TD>$//;
+    $strdata =~ s/<BR>/\n/g;
+    my @data = split('</TD><TD>',$strdata);
+    return @data;
+}
+
+sub tableddl(){
+    my $tablename = shift;
+    my $tmpcols = shift;
+    my @cols = @{$tmpcols};
+    my $ddl = 'CREATE TABLE "'.$tablename.'" ('."\n";
+    my @dcol = ();
+    my $pk = "";
+    foreach my $c (@cols){
+        if ($c eq "id"){
+            $pk = "id";
+        }
+        push (@dcol, '"'.$c.'" TEXT');
+    }
+    if ($pk ne ""){
+        push (@dcol,"PRIMARY KEY ('".$pk."')");
+    } 
+    $ddl .= "\t".join(",\n\t",@dcol)."\n);";
+    return $ddl;
+}
\ No newline at end of file
diff --git a/server/invoicejournalserver.pl b/server/invoicejournalserver.pl
new file mode 100644 (file)
index 0000000..1f1c131
--- /dev/null
@@ -0,0 +1,95 @@
+#!C:\Strawberry\perl\bin\perl.exe
+use strict;
+use File::Basename;
+use Getopt::Long;
+use Time::HiRes;
+use Data::Dumper;
+use lib (dirname($0));
+#use Win32::HideConsole; 
+#hide_console;
+if ($^O eq "MSWin32" ){
+       
+  use lib ("C:/Users/ksaff/Workspace/Apps/invoicejournal/server");
+}
+#if ($^O eq "darwin"){
+# use lib ($ENV{HOME}.'/perl5/lib/perl5');
+#}
+use Plack::Builder; 
+use Plack::App::Directory;
+use Plack::App::File;
+use Plack::App::WrapCGI;  
+use Plack::Middleware::Auth::Basic;
+use Plack::Request; 
+use Plack::Runner;
+use Module::Service;
+use Module::Test;
+use Module::SQLite;
+use Module::PDFExtract;
+use Module::FileSystem; 
+
+
+print $^O."\n";
+print $^X."\n";
+print "Process:".$$."\n";
+print join(@INC)."\n";
+
+
+my @match =  grep { /par-.*inc$/} @INC;
+
+my $basedir = dirname($0);
+if (scalar(@match) > 0){
+       $basedir = $match[0];
+}
+
+my $cfgpath = "";
+print "BASEDIR:".$basedir."\n";
+my $name = basename($0);
+$name =~ s/\.pl$//;
+$name =~ s/\.exe$//;
+
+if ($^O eq "MSWin32"){
+       $cfgpath = $ENV{APPDATA}.'/dks/';
+} elsif ($^O eq "darwin"){
+       $cfgpath = $ENV{HOME}.'/Library/Application Support/dks/';
+} else {
+       $cfgpath = $ENV{HOME}.'/.dks/';
+}
+$cfgpath =~ s/\\/\//g;
+print $cfgpath."\n";
+# sub version {
+#     require Twiggy;
+#     print "Twiggy $Twiggy::VERSION\n";
+# }
+
+# sub authen_cb {
+#       my($username, $password, $env) = @_;
+#       my $auth = 0;
+#       if (-e $cfgpath.'/'.$name.'.passwd'){
+#              open(AUTH,$cfgpath.'/'.$name.'.passwd');
+#              while (my $l = <AUTH>){
+#                      chomp($l);
+#                      if ($l eq $username.'='.$password){
+#                              $auth = 1;
+#                              last;
+#                      }
+#              }
+#              close(AUTH);
+#       }
+#       return $auth;
+#   }
+
+my $allapp = builder {
+   mount "/server" => Module::Service->new({pid => $$ }); 
+   mount "/filesystem" => Module::FileSystem->new({docpath => $cfgpath.'invoicejournal/documents'});
+   mount "/test" => Module::Test->new();
+   mount "/pdfextract" => Module::PDFExtract->new({docpath => $cfgpath.'invoicejournal/documents'});
+   mount "/sqlite" => Module::SQLite->new({dbpath => $cfgpath."db"});
+};
+
+
+my @args = ("-p","6060");
+my $runner = Plack::Runner->new(server => 'Starlight', env => 'deployment');#env => development, test 
+$runner->parse_options(@args);
+$runner->run($allapp);
+
+print "Started\n";
diff --git a/server/pdfextract.pl b/server/pdfextract.pl
new file mode 100644 (file)
index 0000000..26d5748
--- /dev/null
@@ -0,0 +1,155 @@
+#!C:\Perl\bin\perl.exe
+
+use strict;
+use warnings;
+use Getopt::Long;
+use File::Basename;
+my $pdffile="";
+my $toolsdir=dirname($0);
+GetOptions("pdf|p=s" => \$pdffile);
+
+if (! -e $pdffile) {
+  print "incomplete input!\n";
+  exit(1);
+}
+if (-e $pdffile.'.txt'){
+  unlink($pdffile.'.txt');
+}
+if (-e $pdffile.'.csv'){
+  unlink($pdffile.'.csv');
+}
+
+my $sep = '/';
+if ($^O eq "Win32") {
+  $sep = "\\";
+}
+if (! -e $toolsdir.$sep.'pdftotext.exe'){
+  exit(2);
+}
+  my $cmd = '"'.$toolsdir.$sep.'pdftotext.exe" -table -eol unix "'.$pdffile.'" "'.$pdffile.'.txt"';
+  my $st = system($cmd);
+  my $jdata = ();
+  if (($st == 0) && (-e $pdffile.".txt")){
+    my @pdata = ();
+    open(PDFDATA,$pdffile.".txt");
+    while (my $l = <PDFDATA>) {
+      chomp($l);
+      if ($l ne "") {
+        push @pdata,$l;
+      }
+    }
+    close(PDFDATA);
+    my $caccount = "";
+    my $stmtnum = "";
+    my $r = 0;
+    my $cpos = "";
+    foreach my $p (@pdata){
+      if ($p =~ /^\s+Konto\s+:/ ) {
+        $cpos = "";
+        ($caccount) = $p =~ m/.+IBAN\s+(.+)$/;
+        next;
+      }elsif ($p =~ /Kontoauszug Nr\./){
+        $cpos = "";
+        ($stmtnum) = $p =~ m/^Kontoauszug Nr\.\s(\d+).+$/;
+        next;
+      }elsif ($p =~ /^\d\d\.\d\d\s+[GUT|UEBER|SEPA]/){
+        $cpos = "";
+        $r++;
+        my ($type,$trdate,$trval,$trsign) = $p =~ m/\d\d\.\d\d\s+(.+)\s+(\d\d\.\d\d\.\d\d)\s+([\d|,|\.]+)\s([+|-])$/; 
+        $type =~ s/\s//g;
+        $trsign =~ s/\+//;
+        $trval=~ s/\.//g;
+        $trdate = substr($trdate,0,6).'20'.substr($trdate,-2);
+        $jdata->{$r}->{"Account"} = $caccount;
+        $jdata->{$r}->{"StatementNumber"} = $stmtnum;
+        $jdata->{$r}->{"BookingDate"} = $trdate;
+        $jdata->{$r}->{"Amount"} = $trsign.$trval;
+        $jdata->{$r}->{"TransactionIdent"} = "";
+        $jdata->{$r}->{"Message"} = "";
+        $jdata->{$r}->{"ForeignAccountOwner"} = "";
+        $jdata->{$r}->{"Bank"} = "";
+        $jdata->{$r}->{"TransferAccount"} = "";
+        $jdata->{$r}->{"TransferCosts"} = 0;
+        #$jdata->{$r}->{"BookingType"} = $type;
+        next;
+      }elsif ($p =~ /^\s+Unser Zeichen/){
+        $cpos = "TransactionIdent";
+        my ($trid) = $p =~ m/^\s+Unser Zeichen\s+(.+)$/;
+        $jdata->{$r}->{$cpos} =$trid;
+      }elsif ($p =~ /^\s+Mitteilung/){
+        $cpos = "Message";
+        my ($msg) = $p =~ m/^\s+Mitteilung\s+(.+)$/;
+        $jdata->{$r}->{$cpos} = $msg;
+      }elsif ($p =~ /^\s+Auftraggeber/){
+        $cpos = "ForeignAccountOwner";
+        my ($apl) = $p =~ /^\s+Auftraggeber\s+(.+)$/;
+        $apl =~ s/\s+/\ /g;
+        $jdata->{$r}->{$cpos} =$apl;
+      }elsif ($p =~ /^\s+Bank d. Auftr.gebers/){
+        $cpos = "";
+      }elsif ($p =~ /^\s+BIC-Code Bank d. Auftraggebers/){
+        $cpos = "Bank";
+        my ($trfbank) = $p =~ /^\s+BIC-Code\sBank\sd\.\sAuftraggebers\s+(.+)$/;
+        $jdata->{$r}->{$cpos} =$trfbank;
+      }elsif ($p =~ /^\s+End-to-End-Identifizierung/){
+        $cpos = "";
+      }elsif ($p =~ /^\s+Beg.nstigter/){
+        $cpos = "ForeignAccountOwner";
+        my ($recp) = $p =~ /^\s+Beg.nstigter\s+(.+)$/;
+        $recp =~ s/\s+/\ /g;
+        $jdata->{$r}->{$cpos} =$recp;
+        $cpos="";
+      }elsif ($p =~ /^\s+Konto Nr. Beg.nst./){
+        $cpos = "TransferAccount";
+        
+        my ($trfacc) = $p =~ /^\s+Konto\sNr\.\sBeg.nst.\s+(.+)$/;
+        $trfacc =~ s/\///g;
+        $trfacc =~ s/(....)/$1 /sg;
+        $trfacc =~ s/\s+$//;
+        $jdata->{$r}->{$cpos} =$trfacc;
+        $cpos="";
+      }elsif ($p =~ /^\s+bei/){
+        $cpos = "";
+      }elsif ($p =~ /^\s+Transfergeb.hr/){
+        $cpos = "TransferCosts";
+        my ($tramount) = $p =~ /^\s+Transfergeb.hr\s+EUR\s+(.+)$/;
+        $tramount =~ s/\,/\./g;
+        $jdata->{$r}->{$cpos} =$tramount;
+      }elsif ($p =~ /^\s+Durch Ihren Bonus abgedeckt/){
+        $cpos = "TransferCosts";
+        my ($tramount) = $p =~ /^\s+Durch Ihren Bonus abgedeckt\s+EUR\s+(.+)$/;
+        $tramount =~ s/\,/\./g;
+        $jdata->{$r}->{$cpos} = $jdata->{$r}->{$cpos} + $tramount;
+      }elsif ($p =~ /^\s+Zeichen/){
+        $cpos = "";
+      } elsif ($p =~ /^\s+Neuer Kontostand/){
+        $cpos="";
+      }elsif ($cpos ne "") {
+        my ($data) = $p =~ m/\s+(.+)$/;
+        $jdata->{$r}->{$cpos} .= " ".$data;
+      }
+    }
+
+  }
+if (-e $pdffile.'.txt'){
+  unlink($pdffile.'.txt');
+}
+open(FOUT,">".$pdffile.".csv");
+print FOUT '"Account","StatementNumber","BookingDate","Amount","TransactionIdent","Message","ForeignAccountOwner","Bank","TransferAccount","TransferCosts"'."\n";
+foreach my $r (keys(%{$jdata})){
+  my @data = ();
+  push @data, $jdata->{$r}->{"Account"};
+  push @data, $jdata->{$r}->{"StatementNumber"};
+  push @data, $jdata->{$r}->{"BookingDate"};
+  push @data, $jdata->{$r}->{"Amount"};
+  push @data, $jdata->{$r}->{"TransactionIdent"};
+  $jdata->{$r}->{"Message"} =~ s/\s+/\ /g;
+  push @data, $jdata->{$r}->{"Message"};
+  $jdata->{$r}->{"ForeignAccountOwner"} =~ s/\s+/\ /g;
+  push @data, $jdata->{$r}->{"ForeignAccountOwner"};
+  push @data, $jdata->{$r}->{"Bank"};
+  push @data, $jdata->{$r}->{"TransferAccount"};
+  push @data, $jdata->{$r}->{"TransferCosts"};
+  print FOUT '"'.join('","',@data).'"'."\n";
+}
+close(FOUT);
\ No newline at end of file
diff --git a/server/pdftextblock.pl b/server/pdftextblock.pl
new file mode 100644 (file)
index 0000000..c4e548b
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/env perl
+use strict;
+use PDF::API2;
+use PDF::TextBlock;
+
+my $pdf = PDF::API2->new( -file => "40-demo.pdf" );
+my $tb  = PDF::TextBlock->new({
+   
+   pdf       => $pdf,
+   fonts     => {
+      b => PDF::TextBlock::Font->new({
+         pdf  => $pdf,
+         font => $pdf->corefont( 'Helvetica-Bold', -encoding => 'latin1' ),
+      }),
+      i => PDF::TextBlock::Font->new({
+        pdf => $pdf,
+        font => $pdf->corefont('Helvetica-Oblique')
+      })
+   },
+});
+$tb->text(
+   ' <b>This fairly lengthy</b>, rather <i>verbose sentence</i> <b>is tagged</b> to appear ' .
+   ' <href="http://www.dks.lu">Click here to visit Omni Hotels.</href> ' . "\n\n" .
+   "New paragraph.\n\n" .
+   "Another paragraph."
+);
+$tb->apply;
+$pdf->save;
+$pdf->end;
\ No newline at end of file
diff --git a/server/pdftotext.exe b/server/pdftotext.exe
new file mode 100644 (file)
index 0000000..3cfe847
Binary files /dev/null and b/server/pdftotext.exe differ
diff --git a/server/sqlitedumpdata.pl b/server/sqlitedumpdata.pl
new file mode 100644 (file)
index 0000000..00ad087
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+use strict;
+use File::Basename;
+use Getopt::Long;
+use lib (dirname($0).'/cgi/lib');
+use sqlite;
+use pgsql;
+use localconfig;
+my $sdb = "";
+my $ident = "";
+my $schema = 0;
+my $data = 0;
+GetOptions("database|db=s" => \$sdb,"ident|i=s" => \$ident,"schema|s" => \$schema,"data|d" => \$data);
+my $db = sqlite->new($sdb);
+
+$db->dbbackup(dirname($0).'/cursqlitedb.sql','sql');
+my $cfgpath=dirname($0).'/conf';
+my $cfg = localconfig->new($cfgpath.'/'.$ident.'.conf');
+my $rcfg = $cfg->readconfig();
+my $pg = pgsql->new($rcfg);
+if ($schema == 1){
+  open(SDB,dirname($0).'/cursqlitedb.sql');
+  my $ddlschema = "";
+  while (my $l = <SDB>){
+   #chomp($l);
+   if (($l !~ /^INSERT/) && ($l !~ /^UPDATE/) && ($l !~ /^DELETE/)){
+    $ddlschema .= $l;
+   }
+  
+  }
+  my $r = $pg->dbexec("INSERT INTO ".$rcfg->{schema}.".ddlschema (ddlschema) VALUES ('".$pg->strreplace($ddlschema)."');");
+}
+if ($data == 1){
+ my @sqldata = ();
+ open(SDB,dirname($0).'/cursqlitedb.sql');
+ while (my $l = <SDB>){
+  chomp($l);
+  if (($l =~ /^INSERT/) || ($l =~ /^UPDATE/) || ($l =~ /^DELETE/)){
+   push(@sqldata,$l);
+  } 
+ }
+ close(SDB);
+ foreach my $d (@sqldata){
+   my $r = $pg->dbexec("INSERT INTO ".$rcfg->{schema}.".sqlsync (tsquery,sqlquery,client) VALUES (now(),'".$pg->strreplace($d)."','system');");
+ }
+}
diff --git a/server/syncfolder.pl b/server/syncfolder.pl
new file mode 100644 (file)
index 0000000..1bde6bf
--- /dev/null
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+use strict;
+my $homepath=$ENV{HOME};
+my $macpath='osascript -e "set mypath to POSIX path of (choose folder with prompt \"Choose Folder:\" default location \"'.$homepath.'\")"';
+my $mirror='dks@dks-backup:/home/dks/mirror/';
+my $ssh= ' -e "ssh -p 3587" ';
+my $path = `$macpath`;
+
+#todo -> show differences + dryrun + keep newer files
+#todo -> rsync with perl (Net::OpenSSH) or putty
+
+if ($path ne ""){
+       chomp($path);
+       my $relpath = substr($path,length($homepath)+1);
+       print "Path to sync: ".$path." -> ".$relpath."\n";
+       my $cmd = 'rsync -avz --progress --delete-after '.$ssh." ".$path." ".$mirror.$relpath;
+       my $terminalcmd = 'osascript -e "tell application \"Terminal\"
+               set currenttab to do script (\"rsync -avz --delete-after --progress -e \'ssh -p 3587\' '.$path.' '.$mirror.$relpath.' && exit 0\")
+       end tell"';
+       
+       print $terminalcmd."\n";
+       system($terminalcmd);
+       
+}
+  
+