[ROSEV_JAMEICAPLUGIN]

After 7 years of tedious manual work, it's finally time to automate importing donations from PayPal. This has become even less trivial since we have EUR and USD accounts.
This enhancement to our plugin for the Jameica Platform does all the work from retrieving data using the PayPal NVP API over normalizing donor names to performing currency conversions for non-EUR donations. Corner cases like non-latin characters, anonymous donations and fake donations are also handled.
ReactOS Deutschland e.V. accounting is now just a matter of minutes instead of days.

The plugin features an intuitive GUI and integrates with our foundation management software JVerein.
It can hopefully be useful to other Germany-based foundations (e.V.) as well.

I'm also using this commit to make the entire plugin code warning-free and removing usage of deprecated elements.

Pardon my Java ;)

Fixes ONLINE-629

svn path=/trunk/rosev_jameicaplugin/; revision=2279
This commit is contained in:
Colin Finck 2016-08-24 09:42:56 +00:00
parent f220d61b23
commit f5c97d4f9d
45 changed files with 2129 additions and 85 deletions

View File

@ -4,5 +4,6 @@
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry combineaccessrules="false" kind="src" path="/jverein"/>
<classpathentry combineaccessrules="false" kind="src" path="/jameica"/>
<classpathentry kind="lib" path="lib/junidecode-0.2.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -1,6 +1,6 @@
#Thu Aug 19 19:30:43 CEST 2010
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6

BIN
lib/junidecode-0.2.jar Normal file

Binary file not shown.

View File

@ -23,6 +23,7 @@
<navigation>
<item name="ReactOS Deutschland e.V. Helper Plugin" icon-close="folder.png" icon-open="folder-open.png" action="org.reactos.ev.jameicaplugin.gui.action.Welcome">
<item name="Donation Importer" icon-close="document-save.png" action="org.reactos.ev.jameicaplugin.gui.action.DonationImporter" />
<item name="Public Donation List" icon-close="preferences-system-windows.png" action="org.reactos.ev.jameicaplugin.gui.action.PublicDonationList" />
</item>
</navigation>

View File

@ -16,12 +16,23 @@ CREATE TABLE `additional_donations` (
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table "exchange_rates"
CREATE TABLE `exchange_rates` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`year` int(4) unsigned NOT NULL,
`month` int(2) unsigned NOT NULL,
`currency_code` char(3) NOT NULL,
`exchange_rate_to_eur` double unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Table "version"
CREATE TABLE `version` (
`version` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `version` VALUES (1);
INSERT INTO `version` VALUES (2);
-- Stored function "get_jverein_amount"

View File

@ -0,0 +1,20 @@
<form>
<p>
<span color="header" font="header">Spenden-Importierer</span>
</p>
<p>
Dieses Werkzeug ermöglicht es Ihnen, Spenden aus PayPal in JVerein's Buchhaltung zu importieren.
</p>
<p>
Zuerst müssen Sie API-Zugangsdaten für Ihr PayPal-Konto anfordern und im Bereich <b>PayPal API-Zugangsdaten</b> eintragen.
Wenn Nicht-EUR-Spenden importiert werden, müssen Sie außerdem die monatlichen Umrechnungskurse über den Button <b>Umrechnungskurse</b> eintragen.
</p>
<p>
Geben Sie dann Start- und Enddatum der zu importierenden Spenden ein und klicken Sie auf <b>PayPal-Transaktionen herunterladen</b>.
Überprüfen Sie die heruntergeladenen Spenden für jede Währung, ändern Sie deren Details und verschieben Sie diese zwischen beiden Tabellen nach Belieben.
Klicken Sie abschließend auf den Button <b>Spenden in JVerein importieren</b>. Dadurch werden alle Spenden aus der Spenden-Tabelle in JVerein importiert.
</p>
</form>

View File

@ -16,4 +16,4 @@
Die Checkbox <b>Nur ungültige Spenden anzeigen</b> zeigt alle Spenden mit einem Betrag von 0 an. Dies kann passieren, wenn z.B.
der Spendenbetrag in einer Spende aus JVerein nicht oder nicht richtig eingegeben wurde.
</p>
</form>
</form>

View File

@ -0,0 +1,20 @@
<form>
<p>
<span color="header" font="header">Donation Importer</span>
</p>
<p>
This tool allows you to import donations from PayPal into JVerein's accounting.
</p>
<p>
First request API Credentials for your PayPal account and enter the details at <b>PayPal API Credentials</b>.
When importing non-EUR donations, you also have to enter the monthly exchange rates using the <b>Exchange Rates</b> button.
</p>
<p>
Then enter start and end date of the donations you want to import and click <b>Download PayPal Transactions</b>.
Check the downloaded donations for each currency, modify their details and move them between both tables at will.
Finally click the button <b>Import Donations in JVerein</b>. This will import all donations from the Donations table to JVerein.
</p>
</form>

View File

@ -16,4 +16,4 @@
The checkbox <b>Show invalid donations only</b> shows all donations with an amount of 0. This can happen for example if a JVerein
booking has no donation amount or if it has been entered wrongly.
</p>
</form>
</form>

BIN
src/img/go-down.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 683 B

BIN
src/img/go-up.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

View File

@ -1,3 +1,5 @@
\+\ Donations\:=+ Spenden:
%s\ donations\ imported\ into\ JVerein=%s-Spenden in JVerein importiert
About=Info
Additional\ donation=Zusätzliche Spende
Amount=Betrag
@ -5,27 +7,56 @@ Anonymous=Anonym
Close=Schließen
Comment=Kommentar
Copy\ to\ clipboard=In die Zwischenablage kopieren
Currency=Währung
Currency\ Code=Währungscode
Date=Datum
Delete=Löschen
Delete\ donations=Spenden löschen
Do\ you\ really\ want\ to\ delete\ these\ donations?=Möchten Sie diese Spenden wirklich löschen?
Donation=Spende
Public\ Donation\ List=Öffentliche Spender-Liste
Donation\ Importer=Spenden-Importierer
Donation\ saved=Spende gespeichert
Donations\ deleted=Spenden gelöscht
Donations\ to\ import=Zu importierende Spenden
Download\ PayPal\ Transactions=PayPal-Transaktionen herunterladen
Downloading\ PayPal\ Transactions...=PayPal-Transaktionen werden heruntergeladen...
Duplicates\ and\ other\ Transactions=Duplikate und andere Transaktionen
Edit=Bearbeiten
End\ Date=Enddatum
Enter\ the\ data\ from\ "%s"\ for\ the\ currencies\ you\ use\ here.=Geben Sie hier die Daten aus "%s" für die genutzten Währungen ein.
Exchange\ Rate\ to\ EUR=Umrechnungskurs in EUR
Exchange\ Rates=Umrechnungskurse
Gross\ Amount=Bruttobetrag
HTML\ Output=HTML-Ausgabe
Import\ Donations\ into\ JVerein=Spenden in JVerein importieren
Invalid\ value=Ungültiger Wert
JVerein\ Account\:=JVerein-Konto:
JVerein\ Account\ with\ Donations\:=JVerein-Konto mit Spenden:
Month=Monat
Name=Name
Net\ Amount=Nettobetrag
New\ additional\ donation=Neue zusätzliche Spende
No\ exchange\ rate\ entered\ for\ %s=Kein Umrechnungskurs für %s eingegeben
No\ donation\ selected=Keine Spende ausgewählt
Password=Passwort
PayPal\ Account\:=PayPal-Konto:
PayPal\ API\ Credentials=PayPal API-Zugangsdaten
Please\ enter\ a\ date=Bitte geben Sie ein Datum ein
Please\ enter\ a\ name=Bitte geben Sie einen Namen ein
Please\ enter\ a\ valid\ exchange\ rate=Bitte geben Sie einen gültigen Umrechnungskurs ein
Please\ enter\ a\ valid\ month=Bitte geben Sie einen gültigen Monat ein
Please\ enter\ a\ valid\ year=Bitte geben Sie ein gültiges Jahr ein
Please\ enter\ an\ amount=Bitte geben Sie einen Betrag ein
Please\ enter\ a\ 3-character\ currency\ code=Bitte geben Sie einen dreistelligen Währungscode ein
Public\ Donation\ List=Öffentliche Spender-Liste
Save=Speichern
Show\ invalid\ donations\ only=Nur ungültige Spenden anzeigen
Signature=Signatur
Source=Quelle
Start\ Date=Startdatum
This\ plugin\ extends\ the\ foundation\ management\ software\ for\ ReactOS-specific\ tasks.=Dieses Plugin erweitert die Vereinsverwaltungssoftware für ReactOS-spezifische Aufgaben.
Totals=Summen
Type=Typ
Username=Benutzername
Year=Jahr
You\ can\ only\ delete\ additional\ donations\ here!=Sie können hier nur zusätzliche Spenden löschen!
Welcome=Willkommen
Welcome=Willkommen

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -10,6 +10,7 @@ package org.reactos.ev.jameicaplugin;
import de.willuhn.datasource.rmi.DBService;
import de.willuhn.jameica.plugin.AbstractPlugin;
import de.willuhn.jameica.system.Application;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
import de.willuhn.util.I18N;
import java.rmi.RemoteException;
@ -22,6 +23,7 @@ import org.reactos.ev.jameicaplugin.server.JameicaPluginDBServiceImpl;
public class JameicaPlugin extends AbstractPlugin
{
public static final DecimalFormat currencyFormat = (DecimalFormat) DecimalFormat.getInstance(Application.getConfig().getLocale());
public static final DecimalFormat currencyFormatUS = (DecimalFormat) DecimalFormat.getInstance(Locale.US);
public static final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, Application.getConfig().getLocale());
private static DBService db;
private static I18N i18n;
@ -30,6 +32,8 @@ public class JameicaPlugin extends AbstractPlugin
{
currencyFormat.setMinimumFractionDigits(2);
currencyFormat.setMaximumFractionDigits(2);
currencyFormatUS.setMinimumFractionDigits(2);
currencyFormatUS.setMaximumFractionDigits(2);
}
public JameicaPlugin()
@ -39,6 +43,7 @@ public class JameicaPlugin extends AbstractPlugin
JameicaPlugin.class.getClassLoader());
}
@Override
public void init() throws ApplicationException
{
// Only check the version on the server if we're in client/server mode
@ -53,13 +58,9 @@ public class JameicaPlugin extends AbstractPlugin
service.start();
service.checkVersion();
}
catch (ApplicationException e)
{
throw e;
}
catch (RemoteException e)
{
throw new ApplicationException("Error initializing the database", e);
Logger.error("Error initializing the database", e);
}
finally
{
@ -71,7 +72,7 @@ public class JameicaPlugin extends AbstractPlugin
}
catch (RemoteException e)
{
throw new ApplicationException("Error closing the database", e);
Logger.error("Error closing the database", e);
}
}
}

View File

@ -0,0 +1,23 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.formatter;
import de.willuhn.jameica.gui.formatter.Formatter;
public class BooleanFormatter implements Formatter
{
@Override
public String format(Object o)
{
Boolean b = (Boolean) o;
if (b)
return "X";
else
return "";
}
}

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -9,26 +9,19 @@ package org.reactos.ev.jameicaplugin.formatter;
import de.willuhn.datasource.rmi.DBIterator;
import de.willuhn.jameica.gui.formatter.Formatter;
import de.willuhn.logging.Logger;
import java.rmi.RemoteException;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
import org.reactos.ev.jameicaplugin.rmi.Donation;
public class HTMLFormatter implements Formatter
{
private static final DecimalFormat currencyFormat = (DecimalFormat) DecimalFormat.getInstance(Locale.US);
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
private static final DateFormat yearFormat = new SimpleDateFormat("yyyy");
static
{
currencyFormat.setMinimumFractionDigits(2);
currencyFormat.setMaximumFractionDigits(2);
}
private String beginNewYear(String year)
{
String html = "<h3>" + year + "</h3>";
@ -49,10 +42,13 @@ public class HTMLFormatter implements Formatter
return "</tbody></table>";
}
@Override
public String format(Object o)
{
String currentYear = "";
DBIterator donationList = (DBIterator) o;
@SuppressWarnings("unchecked")
DBIterator<Donation> donationList = (DBIterator<Donation>) o;
String html = "<!-- Autogenerated by rosev_jameicaplugin -->";
html += dateFormat.format(new Date());
@ -84,7 +80,7 @@ public class HTMLFormatter implements Formatter
html += String.format("<td>%s</td>", d.getName());
html += String.format("<td>%s</td>", dateFormat.format(d.getDate()));
html += String.format("<td>%s %s</td>", d.getCurrency(), currencyFormat.format(d.getAmount()));
html += String.format("<td>%s %s</td>", d.getCurrency(), JameicaPlugin.currencyFormatUS.format(d.getAmount()));
html += "</tr>";
}
@ -92,7 +88,7 @@ public class HTMLFormatter implements Formatter
}
catch (RemoteException e)
{
e.printStackTrace();
Logger.error("Error while formatting as HTML", e);
}
return html;

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -9,19 +9,22 @@ package org.reactos.ev.jameicaplugin.gui.action;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.dialogs.AbstractDialog;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
public class About implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
try
{
new org.reactos.ev.jameicaplugin.gui.dialog.About(AbstractDialog.POSITION_CENTER).open();
new org.reactos.ev.jameicaplugin.gui.dialog.About(
AbstractDialog.POSITION_CENTER).open();
}
catch (Exception e)
{
throw new ApplicationException("Error while opening the About dialog", e);
Logger.error("Error while opening the About dialog", e);
}
}
}

View File

@ -20,6 +20,7 @@ import org.reactos.ev.jameicaplugin.rmi.Donation;
public class DeleteDonation implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
Donation[] donations = null;

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -12,6 +12,7 @@ import de.jost_net.JVerein.gui.action.BuchungAction;
import de.jost_net.JVerein.rmi.Buchung;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.GUI;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
import java.rmi.RemoteException;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
@ -21,6 +22,7 @@ import org.reactos.ev.jameicaplugin.rmi.Donation;
public class DonationDetail implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
Donation d = (Donation) context;
@ -44,7 +46,7 @@ public class DonationDetail implements Action
}
catch (RemoteException e)
{
throw new ApplicationException("Error while handling the donation action!");
Logger.error("Error while handling the donation action", e);
}
}
}

View File

@ -0,0 +1,21 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.gui.action;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.GUI;
import de.willuhn.util.ApplicationException;
public class DonationImporter implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
GUI.startView(org.reactos.ev.jameicaplugin.gui.view.DonationImporter.class.getName(), null);
}
}

View File

@ -0,0 +1,30 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.gui.action;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.dialogs.AbstractDialog;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
public class ExchangeRates implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
try
{
new org.reactos.ev.jameicaplugin.gui.dialog.ExchangeRates(
AbstractDialog.POSITION_CENTER).open();
}
catch (Exception e)
{
Logger.error("Error while opening the Exchange Rates dialog", e);
}
}
}

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -9,19 +9,22 @@ package org.reactos.ev.jameicaplugin.gui.action;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.dialogs.AbstractDialog;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
public class HTMLOutput implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
try
{
new org.reactos.ev.jameicaplugin.gui.dialog.HTMLOutput(AbstractDialog.POSITION_CENTER).open();
new org.reactos.ev.jameicaplugin.gui.dialog.HTMLOutput(
AbstractDialog.POSITION_CENTER).open();
}
catch (Exception e)
{
throw new ApplicationException("Error while opening the HTML Output dialog", e);
Logger.error("Error while opening the HTML Output dialog", e);
}
}
}

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -9,6 +9,7 @@ package org.reactos.ev.jameicaplugin.gui.action;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.GUI;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
import java.rmi.RemoteException;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
@ -17,6 +18,7 @@ import org.reactos.ev.jameicaplugin.rmi.AdditionalDonation;
public class NewAdditionalDonation implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
AdditionalDonation ad;
@ -28,7 +30,7 @@ public class NewAdditionalDonation implements Action
}
catch (RemoteException e)
{
throw new ApplicationException("Error while creating a new additional donation!");
Logger.error("Error while creating a new additional donation", e);
}
}
}

View File

@ -0,0 +1,35 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.gui.action;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.dialogs.AbstractDialog;
import de.willuhn.jameica.system.OperationCanceledException;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
public class PayPalAPICredentials implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
try
{
new org.reactos.ev.jameicaplugin.gui.dialog.PayPalAPICredentials(
AbstractDialog.POSITION_CENTER).open();
}
catch (OperationCanceledException e)
{
// Do nothing
}
catch (Exception e)
{
Logger.error("Unable to open the PayPal API Credentials dialog", e);
}
}
}

View File

@ -13,6 +13,7 @@ import de.willuhn.util.ApplicationException;
public class PublicDonationList implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
GUI.startView(org.reactos.ev.jameicaplugin.gui.view.PublicDonationList.class.getName(), null);

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -13,6 +13,7 @@ import de.willuhn.util.ApplicationException;
public class Welcome implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
GUI.startView(org.reactos.ev.jameicaplugin.gui.view.Welcome.class.getName(), null);

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -37,7 +37,7 @@ public class DonationControl extends AbstractControl
public Part getDonationList() throws RemoteException
{
DBIterator donations = JameicaPlugin.getDBService().createList(Donation.class);
DBIterator<Donation> donations = JameicaPlugin.getDBService().createList(Donation.class);
// Invalid donations have "amount" set to zero due to the way the VIEW
// is defined
@ -52,7 +52,7 @@ public class DonationControl extends AbstractControl
donationList.addColumn(JameicaPlugin.i18n().tr("Name"), "name");
donationList.addColumn(JameicaPlugin.i18n().tr("Amount"), "amount", new CurrencyFormatter(
"", JameicaPlugin.currencyFormat));
donationList.addColumn(JameicaPlugin.i18n().tr("Currency"), "currency");
donationList.addColumn(JameicaPlugin.i18n().tr("Currency Code"), "currency");
donationList.addColumn(JameicaPlugin.i18n().tr("Source"), "jverein_id", new Formatter()
{
public String format(Object o)

View File

@ -0,0 +1,278 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.gui.control;
import de.willuhn.jameica.gui.AbstractControl;
import de.willuhn.jameica.gui.AbstractView;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.GUI;
import de.willuhn.jameica.gui.parts.Button;
import de.willuhn.jameica.gui.parts.TablePart;
import de.willuhn.jameica.gui.util.SimpleContainer;
import de.willuhn.jameica.gui.util.TabGroup;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
import java.rmi.RemoteException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
import org.reactos.ev.jameicaplugin.io.JVereinIO;
import org.reactos.ev.jameicaplugin.io.Transaction;
public class TransactionTabControl extends AbstractControl
{
private Label accountTotalLabel;
private Label donationTotalLabel;
private Label jVereinTotalLabel;
private Label newJVereinTotalLabel;
private TablePart donationTable;
private TablePart otherTable;
private String currencyCode;
private ArrayList<Transaction> transactions;
private Double accountTotal = 0.0;
private Double donationTotal = 0.0;
private Double jVereinTotal = 0.0;
private class AddToJVereinAction implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
try
{
if (transactions == null || transactions.isEmpty())
return;
JVereinIO jVereinIO = new JVereinIO();
// For currencies other than EUR, we need the monthly exchange
// rates to add them.
// Make sure that the user entered these.
if (!currencyCode.equals("EUR"))
{
for (Transaction t : transactions)
{
if (jVereinIO.getExchangeRateToEUR(t.getDate(), currencyCode) == 0)
{
throw new ApplicationException(
String.format(JameicaPlugin.i18n().tr("No exchange rate entered for %s"), new SimpleDateFormat(
"yyyy-MM").format(t.getDate())));
}
}
}
// Add the donations to JVerein.
jVereinIO.putDonationTransactions(transactions, currencyCode);
// Update totals now that the donations have been added and
// empty the tables.
setJVereinTotal(jVereinTotal + donationTotal);
transactions.clear();
setTransactions(transactions, currencyCode);
// Report success.
GUI.getStatusBar().setSuccessText(String.format(JameicaPlugin.i18n().tr("%s donations imported into JVerein"), currencyCode));
}
catch (RemoteException e)
{
Logger.error("Error while adding JVerein transactions", e);
}
}
}
private class TransactionUpDownAction implements Action
{
@Override
public void handleAction(Object context) throws ApplicationException
{
TablePart table = (TablePart) context;
Object selection = table.getSelection();
if (selection == null)
return;
// Check if one or multiple transactions are selected.
Transaction[] selectedTransactions;
if (selection instanceof Transaction)
{
selectedTransactions = new Transaction[1];
selectedTransactions[0] = (Transaction) selection;
}
else
{
selectedTransactions = (Transaction[]) selection;
}
for (Transaction t : selectedTransactions)
{
try
{
// Flip the donation bit and alter the tables accordingly.
t.setDonation(!t.isDonation());
table.removeItem(t);
addTransactionToTable(t, true);
updateDonationTotal();
}
catch (RemoteException e)
{
Logger.error("Error while altering transaction tables", e);
}
}
}
}
private Label addTotalLabel(Composite composite, String caption)
{
final GridData leftGridData = new GridData();
leftGridData.widthHint = 200;
final GridData rightGridData = new GridData(GridData.HORIZONTAL_ALIGN_END);
rightGridData.widthHint = 50;
final Label captionLabel = new Label(composite, SWT.NONE);
captionLabel.setLayoutData(leftGridData);
captionLabel.setText(caption);
final Label label = new Label(composite, SWT.RIGHT);
label.setLayoutData(rightGridData);
return label;
}
private void addTransactionToTable(Transaction t, Boolean subtract) throws RemoteException
{
if (t.isDonation())
{
donationTable.addItem(t);
donationTotal += t.getNetAmount();
}
else
{
otherTable.addItem(t);
if (subtract)
donationTotal -= t.getNetAmount();
}
}
private void updateDonationTotal()
{
Double newJVereinTotal = jVereinTotal + donationTotal;
donationTotalLabel.setText(JameicaPlugin.currencyFormat.format(donationTotal));
newJVereinTotalLabel.setText(JameicaPlugin.currencyFormat.format(newJVereinTotal));
}
public TransactionTabControl(AbstractView view)
{
super(view);
}
public void add(TabGroup tabGroup) throws RemoteException
{
final SimpleContainer leftContainer = new SimpleContainer(tabGroup.getComposite(), true);
// Create both tables.
donationTable = new TransactionTableControl(view, true).getTable();
otherTable = new TransactionTableControl(view, false).getTable();
// Paint the donation table.
leftContainer.addHeadline(JameicaPlugin.i18n().tr("Donations to import"));
donationTable.paint(leftContainer.getComposite());
// Create the buttons for moving transactions around.
leftContainer.addSeparator();
final Composite tableButtonsComposite = new Composite(leftContainer.getComposite(),
SWT.NONE);
tableButtonsComposite.setLayout(new GridLayout(2, true));
tableButtonsComposite.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false));
final Button down = new Button(null, new TransactionUpDownAction(), donationTable);
down.setIcon("go-down.png");
down.paint(tableButtonsComposite);
final Button up = new Button(null, new TransactionUpDownAction(), otherTable);
up.setIcon("go-up.png");
up.paint(tableButtonsComposite);
// Paint the table for other transactions.
leftContainer.addHeadline(JameicaPlugin.i18n().tr("Duplicates and other Transactions"));
otherTable.paint(leftContainer.getComposite());
// Create the right container.
// Fill vertical, but not horizontal, so that the right container does
// not occupy half of the tab width.
final SimpleContainer rightContainer = new SimpleContainer(tabGroup.getComposite());
rightContainer.getComposite().setLayout(new GridLayout());
rightContainer.getComposite().setLayoutData(new GridData(
GridData.FILL_VERTICAL | GridData.VERTICAL_ALIGN_BEGINNING));
// Create the labels for totals.
rightContainer.addHeadline(JameicaPlugin.i18n().tr("Totals"));
final Composite topLabelsComposite = new Composite(rightContainer.getComposite(), SWT.NONE);
topLabelsComposite.setLayout(new GridLayout(2, false));
accountTotalLabel = addTotalLabel(topLabelsComposite, JameicaPlugin.i18n().tr("PayPal Account:"));
jVereinTotalLabel = addTotalLabel(topLabelsComposite, JameicaPlugin.i18n().tr("JVerein Account:"));
donationTotalLabel = addTotalLabel(topLabelsComposite, JameicaPlugin.i18n().tr("+ Donations:"));
rightContainer.addSeparator();
final Composite bottomLabelsComposite = new Composite(rightContainer.getComposite(),
SWT.NONE);
bottomLabelsComposite.setLayout(new GridLayout(2, false));
newJVereinTotalLabel = addTotalLabel(bottomLabelsComposite, JameicaPlugin.i18n().tr("JVerein Account with Donations:"));
// Finally put the "Add" button in the bottom-right corner of the tab.
Composite addButtonComposite = new Composite(rightContainer.getComposite(), SWT.NONE);
addButtonComposite.setLayout(new GridLayout());
addButtonComposite.setLayoutData(new GridData(GridData.FILL_VERTICAL
| GridData.HORIZONTAL_ALIGN_END | GridData.VERTICAL_ALIGN_END));
Button add = new Button(JameicaPlugin.i18n().tr("Import Donations into JVerein"),
new AddToJVereinAction(), null, false, "list-add.png");
add.paint(addButtonComposite);
}
public void setAccountTotal(Double total)
{
accountTotal = total;
accountTotalLabel.setText(JameicaPlugin.currencyFormat.format(accountTotal));
}
public void setJVereinTotal(Double total)
{
jVereinTotal = total;
jVereinTotalLabel.setText(JameicaPlugin.currencyFormat.format(jVereinTotal));
}
public void setTransactions(ArrayList<Transaction> transactions, String currencyCode)
throws RemoteException
{
this.currencyCode = currencyCode;
this.transactions = transactions;
donationTable.removeAll();
otherTable.removeAll();
donationTotal = 0.0;
for (Transaction t : this.transactions)
{
addTransactionToTable(t, false);
}
updateDonationTotal();
}
}

View File

@ -0,0 +1,104 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.gui.control;
import de.willuhn.jameica.gui.AbstractControl;
import de.willuhn.jameica.gui.AbstractView;
import de.willuhn.jameica.gui.formatter.CurrencyFormatter;
import de.willuhn.jameica.gui.formatter.DateFormatter;
import de.willuhn.jameica.gui.parts.Column;
import de.willuhn.jameica.gui.parts.TableChangeListener;
import de.willuhn.jameica.gui.parts.TablePart;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
import java.rmi.RemoteException;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
import org.reactos.ev.jameicaplugin.formatter.BooleanFormatter;
import org.reactos.ev.jameicaplugin.io.Transaction;
public class TransactionTableControl extends AbstractControl
{
private Boolean changeable;
private TablePart table;
public TransactionTableControl(AbstractView view, Boolean changeable)
{
super(view);
this.changeable = changeable;
}
public TablePart getTable()
{
if (table != null)
return table;
// Add the transaction table.
// As this list is empty at start, Jameica has no means of figuring out
// the value types and thus doesn't know when to right-align columns.
// Therefore, we set the alignment manually here.
table = new TablePart(null);
table.addColumn("ID", "id");
table.addColumn(JameicaPlugin.i18n().tr("Date"), "date", new DateFormatter(
JameicaPlugin.dateFormat));
table.addColumn(JameicaPlugin.i18n().tr("Name"), "name", null, changeable);
table.addColumn(JameicaPlugin.i18n().tr("Gross Amount"), "grossamount", new CurrencyFormatter(
"", JameicaPlugin.currencyFormat), false, Column.ALIGN_RIGHT);
table.addColumn(JameicaPlugin.i18n().tr("Net Amount"), "netamount", new CurrencyFormatter(
"", JameicaPlugin.currencyFormat), false, Column.ALIGN_RIGHT);
table.addColumn(JameicaPlugin.i18n().tr("Anonymous"), "anonymous", new BooleanFormatter(), changeable);
table.addColumn(JameicaPlugin.i18n().tr("Comment"), "comment");
table.addColumn(JameicaPlugin.i18n().tr("Type"), "type");
if (changeable)
{
// Enable the user to edit some fields of the transaction list to
// correct mistakes.
table.addChangeListener(new TableChangeListener()
{
@Override
public void itemChanged(Object object, String attribute, String newValue)
throws ApplicationException
{
Transaction t = (Transaction) object;
if (attribute.equals("name"))
{
t.setName(newValue);
}
else if (attribute.equals("anonymous"))
{
Boolean b = false;
if (newValue.equalsIgnoreCase("x"))
b = true;
t.setAnonymous(b);
}
// Ensure that the table shows exactly the data from the
// Transaction object.
try
{
table.updateItem(t, t);
}
catch (RemoteException e)
{
Logger.error("Unable to reload transactions", e);
}
}
});
}
table.setMulti(true);
table.setRememberColWidths(true);
table.setRememberOrder(true);
table.setRememberState(true);
table.setSummary(true);
return table;
}
}

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -10,17 +10,19 @@ package org.reactos.ev.jameicaplugin.gui.dialog;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.GUI;
import de.willuhn.jameica.gui.dialogs.AbstractDialog;
import de.willuhn.jameica.gui.parts.Button;
import de.willuhn.jameica.gui.parts.FormTextPart;
import de.willuhn.jameica.gui.util.ButtonArea;
import de.willuhn.jameica.gui.util.LabelGroup;
import de.willuhn.jameica.gui.util.SWTUtil;
import de.willuhn.jameica.plugin.AbstractPlugin;
import de.willuhn.jameica.system.Application;
import de.willuhn.util.ApplicationException;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
public class About extends AbstractDialog
public class About extends AbstractDialog<Object>
{
public About(int position)
{
@ -29,6 +31,7 @@ public class About extends AbstractDialog
setPanelText("ReactOS Deutschland e.V. Helper Plugin");
}
@Override
protected void paint(Composite parent) throws Exception
{
Label l = GUI.getStyleFactory().createLabel(parent, SWT.BORDER);
@ -36,26 +39,35 @@ public class About extends AbstractDialog
LabelGroup group = new LabelGroup(parent, JameicaPlugin.i18n().tr("About"));
String str = "<form>";
str += "<p><b>ReactOS Deutschland e.V. Helper Plugin</b></p>";
AbstractPlugin p = Application.getPluginLoader().getPlugin(JameicaPlugin.class);
str += "<p>Version " + p.getManifest().getVersion() + "</p>";
str += "<p>Licence: GPLv2 or any later version (http://www.gnu.org/copyleft/gpl.html)</p>";
str += "<p>Copyright 2010-2016 ReactOS Deutschland e.V. (deutschland@reactos.org)</p>";
str += "<p>http://ev.reactos.org</p>";
str += "</form>";
FormTextPart text = new FormTextPart();
text.setText("<form>"
+ "<p><b>ReactOS Deutschland e.V. Helper Plugin</b></p>"
+ "<p>Licence: GPLv2 or any later version (http://www.gnu.org/copyleft/gpl.html)</p>"
+ "<p>Copyright 2010 ReactOS Deutschland e.V. (deutschland@reactos.org)</p>"
+ "<p>http://ev.reactos.org</p>" + "</form>");
text.setText(str);
group.addPart(text);
ButtonArea buttons = group.createButtonArea(1);
buttons.addButton(" " + JameicaPlugin.i18n().tr("Close") + " ", new Action()
{
public void handleAction(Object context) throws ApplicationException
{
close();
}
});
Button closeButton = new Button(" " + JameicaPlugin.i18n().tr("Close") + " ",
new Action()
{
public void handleAction(Object context) throws ApplicationException
{
close();
}
});
closeButton.paint(parent);
getShell().pack();
}
@Override
protected Object getData() throws Exception
{
return null;

View File

@ -0,0 +1,157 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.gui.dialog;
import de.willuhn.datasource.rmi.DBIterator;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.dialogs.AbstractDialog;
import de.willuhn.jameica.gui.parts.Button;
import de.willuhn.jameica.gui.parts.TableChangeListener;
import de.willuhn.jameica.gui.parts.TablePart;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
import java.rmi.RemoteException;
import java.util.Calendar;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
import org.reactos.ev.jameicaplugin.rmi.ExchangeRate;
public class ExchangeRates extends AbstractDialog<Object>
{
private TablePart exchangeRateList;
public ExchangeRates(int position)
{
super(position);
setSize(SWT.DEFAULT, 600);
setTitle(JameicaPlugin.i18n().tr("Exchange Rates"));
}
@Override
protected void paint(Composite parent) throws Exception
{
final Label intro = new Label(parent, SWT.WRAP);
intro.setText(String.format(JameicaPlugin.i18n().tr("Enter the data from \"%s\" for the currencies you use here."), "Umsatzsteuer-Umrechnungskurse"));
final Composite listComposite = new Composite(parent, SWT.NONE);
listComposite.setLayout(new GridLayout(2, false));
listComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
final DBIterator<ExchangeRate> exchangeRates = JameicaPlugin.getDBService().createList(ExchangeRate.class);
exchangeRates.setOrder("ORDER BY year, month, currency_code");
exchangeRateList = new TablePart(exchangeRates, null);
exchangeRateList.addColumn(JameicaPlugin.i18n().tr("Year"), "year", null, true);
exchangeRateList.addColumn(JameicaPlugin.i18n().tr("Month"), "month", null, true);
exchangeRateList.addColumn(JameicaPlugin.i18n().tr("Currency Code"), "currency_code", null, true);
exchangeRateList.addColumn(JameicaPlugin.i18n().tr("Exchange Rate to EUR"), "exchange_rate_to_eur", null, true);
exchangeRateList.setRememberColWidths(true);
exchangeRateList.setRememberState(true);
exchangeRateList.setSummary(false);
exchangeRateList.addChangeListener(new TableChangeListener()
{
@Override
public void itemChanged(Object object, String attribute, String newValue)
throws ApplicationException
{
try
{
ExchangeRate er = (ExchangeRate) object;
if (attribute.equals("year"))
er.setYear(Integer.parseInt(newValue));
else if (attribute.equals("month"))
er.setMonth(Integer.parseInt(newValue));
else if (attribute.equals("currency_code"))
er.setCurrencyCode(newValue);
else if (attribute.equals("exchange_rate_to_eur"))
er.setExchangeRateToEUR(Double.parseDouble(newValue));
er.store();
}
catch (NumberFormatException e)
{
throw new ApplicationException(JameicaPlugin.i18n().tr("Invalid value"));
}
catch (RemoteException e)
{
Logger.error("Unable to change exchange rate", e);
}
}
});
exchangeRateList.paint(listComposite);
final Composite sideButtonsComposite = new Composite(listComposite, SWT.NONE);
sideButtonsComposite.setLayout(new GridLayout());
final Button addButton = new Button(null, new Action()
{
@Override
public void handleAction(Object context) throws ApplicationException
{
try
{
ExchangeRate er = JameicaPlugin.getDBService().createObject(ExchangeRate.class, null);
er.setYear(Calendar.getInstance().get(Calendar.YEAR));
er.setMonth(0);
er.setCurrencyCode("USD");
er.setExchangeRateToEUR(0.0);
er.store();
exchangeRateList.addItem(er);
}
catch (RemoteException e)
{
Logger.error("Unable to add exchange rate", e);
}
}
}, null, false, "list-add.png");
addButton.paint(sideButtonsComposite);
final Button removeButton = new Button(null, new Action()
{
@Override
public void handleAction(Object context) throws ApplicationException
{
try
{
ExchangeRate er = (ExchangeRate) exchangeRateList.getSelection();
if (er == null)
return;
// Just delete the item, it is automatically removed from
// the table.
er.delete();
}
catch (RemoteException e)
{
Logger.error("Unable to remove exchange rate", e);
}
}
}, null, false, "list-remove.png");
removeButton.paint(sideButtonsComposite);
Button close = new Button(" " + JameicaPlugin.i18n().tr("Close") + " ", new Action()
{
public void handleAction(Object context) throws ApplicationException
{
close();
}
});
close.paint(parent);
}
@Override
protected Object getData() throws Exception
{
return null;
}
}

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -11,7 +11,7 @@ import de.willuhn.datasource.rmi.DBIterator;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.GUI;
import de.willuhn.jameica.gui.dialogs.AbstractDialog;
import de.willuhn.jameica.gui.util.ButtonArea;
import de.willuhn.jameica.gui.parts.ButtonArea;
import de.willuhn.jameica.gui.util.Color;
import de.willuhn.util.ApplicationException;
import org.eclipse.swt.SWT;
@ -25,7 +25,7 @@ import org.reactos.ev.jameicaplugin.JameicaPlugin;
import org.reactos.ev.jameicaplugin.formatter.HTMLFormatter;
import org.reactos.ev.jameicaplugin.rmi.Donation;
public class HTMLOutput extends AbstractDialog
public class HTMLOutput extends AbstractDialog<Object>
{
public HTMLOutput(int position)
{
@ -34,19 +34,19 @@ public class HTMLOutput extends AbstractDialog
setTitle(JameicaPlugin.i18n().tr("HTML Output"));
}
@Override
protected void paint(Composite parent) throws Exception
{
DBIterator donationList = JameicaPlugin.getDBService().createList(Donation.class);
DBIterator<Donation> donationList = JameicaPlugin.getDBService().createList(Donation.class);
HTMLFormatter formatter = new HTMLFormatter();
final Text text = new Text(parent, SWT.BORDER | SWT.MULTI | SWT.READ_ONLY | SWT.WRAP
| SWT.V_SCROLL);
text.setForeground(Color.WIDGET_FG.getSWTColor());
text.setBackground(Color.WIDGET_BG.getSWTColor());
final Text text = new Text(parent,
SWT.BORDER | SWT.MULTI | SWT.READ_ONLY | SWT.WRAP | SWT.V_SCROLL);
text.setBackground(Color.BACKGROUND.getSWTColor());
text.setLayoutData(new GridData(GridData.FILL_BOTH));
text.setText(formatter.format(donationList));
ButtonArea buttons = new ButtonArea(parent, 2);
ButtonArea buttons = new ButtonArea();
buttons.addButton(" " + JameicaPlugin.i18n().tr("Copy to clipboard") + " ", new Action()
{
public void handleAction(Object context) throws ApplicationException
@ -64,8 +64,10 @@ public class HTMLOutput extends AbstractDialog
close();
}
});
buttons.paint(parent);
}
@Override
protected Object getData() throws Exception
{
return null;

View File

@ -0,0 +1,73 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.gui.dialog;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.dialogs.AbstractDialog;
import de.willuhn.jameica.gui.input.TextInput;
import de.willuhn.jameica.gui.internal.buttons.Cancel;
import de.willuhn.jameica.gui.parts.Button;
import de.willuhn.jameica.gui.parts.ButtonArea;
import de.willuhn.jameica.gui.util.SimpleContainer;
import de.willuhn.jameica.system.Settings;
import de.willuhn.util.ApplicationException;
import org.eclipse.swt.widgets.Composite;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
public class PayPalAPICredentials extends AbstractDialog<Object>
{
public static final Settings settings = new Settings(PayPalAPICredentials.class);
private TextInput username;
private TextInput password;
private TextInput signature;
public PayPalAPICredentials(int position)
{
super(position);
setTitle(JameicaPlugin.i18n().tr("PayPal API Credentials"));
}
@Override
protected void paint(Composite parent) throws Exception
{
username = new TextInput(settings.getString("username", null));
username.setName(JameicaPlugin.i18n().tr("Username"));
password = new TextInput(settings.getString("password", null));
password.setName(JameicaPlugin.i18n().tr("Password"));
signature = new TextInput(settings.getString("signature", null));
signature.setName(JameicaPlugin.i18n().tr("Signature"));
SimpleContainer container = new SimpleContainer(parent);
container.addInput(username);
container.addInput(password);
container.addInput(signature);
final Button ok = new Button("OK", new Action()
{
public void handleAction(Object context) throws ApplicationException
{
settings.setAttribute("username", (String) username.getValue());
settings.setAttribute("password", (String) password.getValue());
settings.setAttribute("signature", (String) signature.getValue());
close();
}
}, null, true, "ok.png");
ButtonArea buttons = new ButtonArea();
buttons.addButton(ok);
buttons.addButton(new Cancel());
container.addButtonArea(buttons);
}
@Override
protected Object getData() throws Exception
{
return null;
}
}

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -10,8 +10,7 @@ package org.reactos.ev.jameicaplugin.gui.view;
import de.willuhn.jameica.gui.AbstractView;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.GUI;
import de.willuhn.jameica.gui.internal.buttons.Back;
import de.willuhn.jameica.gui.util.ButtonArea;
import de.willuhn.jameica.gui.parts.ButtonArea;
import de.willuhn.jameica.gui.util.LabelGroup;
import de.willuhn.jameica.gui.util.ScrolledContainer;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
@ -20,6 +19,7 @@ import org.reactos.ev.jameicaplugin.gui.control.AdditionalDonationControl;
public class AdditionalDonationDetail extends AbstractView
{
@Override
public void bind() throws Exception
{
GUI.getView().setTitle(JameicaPlugin.i18n().tr("Additional donation"));
@ -34,11 +34,10 @@ public class AdditionalDonationDetail extends AbstractView
group.addLabelPair(JameicaPlugin.i18n().tr("Name"), control.getName());
group.addLabelPair(JameicaPlugin.i18n().tr("Anonymous"), control.getAnonymous());
group.addLabelPair(JameicaPlugin.i18n().tr("Amount"), control.getAmount());
group.addLabelPair(JameicaPlugin.i18n().tr("Currency"), control.getCurrency());
group.addLabelPair(JameicaPlugin.i18n().tr("Currency Code"), control.getCurrency());
group.addLabelPair(JameicaPlugin.i18n().tr("Comment"), control.getComment());
ButtonArea buttons = new ButtonArea(getParent(), 3);
buttons.addButton(new Back(false));
de.willuhn.jameica.gui.parts.ButtonArea buttons = new ButtonArea();
buttons.addButton(JameicaPlugin.i18n().tr("New additional donation"), new NewAdditionalDonation(), null, true, "document-new.png");
buttons.addButton(JameicaPlugin.i18n().tr("Save"), new Action()
{
@ -47,5 +46,6 @@ public class AdditionalDonationDetail extends AbstractView
control.handleStore();
}
}, null, true, "document-save.png");
buttons.paint(getParent());
}
}

View File

@ -0,0 +1,334 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.gui.view;
import de.willuhn.jameica.gui.AbstractView;
import de.willuhn.jameica.gui.Action;
import de.willuhn.jameica.gui.GUI;
import de.willuhn.jameica.gui.dialogs.AbstractDialog;
import de.willuhn.jameica.gui.input.DateInput;
import de.willuhn.jameica.gui.internal.buttons.Cancel;
import de.willuhn.jameica.gui.parts.Button;
import de.willuhn.jameica.gui.parts.ButtonArea;
import de.willuhn.jameica.gui.util.SimpleContainer;
import de.willuhn.jameica.gui.util.TabGroup;
import de.willuhn.jameica.system.OperationCanceledException;
import de.willuhn.jameica.system.Settings;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
import java.rmi.RemoteException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.TabFolder;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
import org.reactos.ev.jameicaplugin.gui.action.ExchangeRates;
import org.reactos.ev.jameicaplugin.gui.action.PayPalAPICredentials;
import org.reactos.ev.jameicaplugin.gui.control.TransactionTabControl;
import org.reactos.ev.jameicaplugin.io.JVereinIO;
import org.reactos.ev.jameicaplugin.io.PayPalIO;
import org.reactos.ev.jameicaplugin.io.Transaction;
public class DonationImporter extends AbstractView
{
private static final SimpleDateFormat yyyyMMddFormat = new SimpleDateFormat("yyyyMMdd");
private static final Settings settings = new Settings(DonationImporter.class);
private static final String[] payPalCurrencies = new String[]
{ "EUR", "USD" };
private DateInput startDateInput;
private DateInput endDateInput;
private TabFolder folder;
private final TransactionTabControl[] tabs = new TransactionTabControl[payPalCurrencies.length];
private class DownloadDialog extends AbstractDialog<Object>
{
private ProgressBar progressBar;
private Date startDate;
private Date endDate;
public DownloadDialog(int position, Date startDate, Date endDate)
{
super(position, false);
this.setSize(600, SWT.DEFAULT);
setTitle(JameicaPlugin.i18n().tr("Downloading PayPal Transactions..."));
this.startDate = startDate;
this.endDate = endDate;
}
@Override
protected void paint(Composite parent) throws Exception
{
// Draw the Progress dialog.
progressBar = new ProgressBar(parent, SWT.SMOOTH);
progressBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
final Button cancel = new Cancel();
cancel.paint(parent);
// Do the time-consuming downloading in a separate thread.
Thread workThread = new Thread()
{
private PayPalIO payPalIO;
private JVereinIO jVereinIO;
private Map<String, Double> balancesMap;
/**
* Compare downloaded PayPal transactions with already existing
* JVerein transactions and mark duplicates in the PayPal
* transactions list.
*/
private void markDuplicateTransactions(ArrayList<Transaction> payPalTransactions,
ArrayList<Transaction> jVereinTransactions)
{
for (Transaction p : payPalTransactions)
{
for (Transaction j : jVereinTransactions)
{
// Only compare year, month and day components of
// the date.
String pDate = yyyyMMddFormat.format(p.getDate());
String jDate = yyyyMMddFormat.format(j.getDate());
if (pDate.equals(jDate) && p.getName().equals(j.getName())
&& p.getGrossAmount().equals(j.getGrossAmount())
&& p.getNetAmount().equals(j.getNetAmount()))
{
// Mark as duplicate by moving it to the other
// table.
p.setDonation(false);
}
}
}
}
/**
* Download the transactions for a single currency.
*
* @param i
* Index of the currency as in payPalCurrencies.
*/
private void downloadCurrencyTransactions(int i) throws RemoteException
{
final String currencyCode = payPalCurrencies[i];
final TransactionTabControl tab = tabs[i];
// Update the totals.
final Double accountTotal = balancesMap.get(currencyCode);
final Double jVereinTotal = jVereinIO.getAccountTotal(currencyCode);
GUI.getDisplay().asyncExec(new Runnable()
{
@Override
public void run()
{
tab.setAccountTotal(accountTotal);
tab.setJVereinTotal(jVereinTotal);
}
});
// Import the PayPal transactions.
final int percentageOffset = i * 100 / payPalCurrencies.length;
final ArrayList<Transaction> payPalTransactions = payPalIO.getTransactions(startDate, endDate, currencyCode, new Action()
{
@Override
public void handleAction(Object context) throws ApplicationException
{
final Integer percentage = (Integer) context;
GUI.getDisplay().asyncExec(new Runnable()
{
@Override
public void run()
{
// If the user clicked Cancel, the progress
// bar may have been disposed, but this
// Runnable is still queued.
// So check for disposal first.
if (!progressBar.isDisposed())
{
// Calculate a total percentage out of
// the per-currency percentage.
progressBar.setSelection(percentageOffset
+ percentage / payPalCurrencies.length);
}
}
});
}
});
// Import the related JVerein transactions.
final ArrayList<Transaction> jVereinTransactions = jVereinIO.getDonationTransactions(startDate, endDate, currencyCode);
// Check for PayPal transactions that have already been
// added to JVerein.
// Mark them as no donations, so they appear in the other
// table and are not imported again.
markDuplicateTransactions(payPalTransactions, jVereinTransactions);
// Finally show the PayPal transactions.
GUI.getDisplay().asyncExec(new Runnable()
{
@Override
public void run()
{
try
{
tab.setTransactions(payPalTransactions, currencyCode);
}
catch (RemoteException e)
{
Logger.error("Unable to set transactions", e);
}
}
});
}
@Override
public void run()
{
try
{
payPalIO = new PayPalIO();
jVereinIO = new JVereinIO();
// Get the PayPal balances.
balancesMap = payPalIO.getBalances();
for (int i = 0; i < payPalCurrencies.length; i++)
{
downloadCurrencyTransactions(i);
}
// Close the dialog when we're done.
// Give the GUI updater Runnables some time to update
// the progress bar before we close (just for eye-candy,
// not required).
sleep(500);
close();
}
catch (Exception e)
{
Logger.error("Error while downloading transactions", e);
}
}
};
workThread.start();
}
@Override
protected Object getData() throws Exception
{
return null;
}
}
@Override
public void bind() throws Exception
{
GUI.getView().setTitle(JameicaPlugin.i18n().tr("Donation Importer"));
// Create a lean layout with less spacing for the control widgets to
// make more room for the tables.
final GridLayout singleColumnLayout = new GridLayout();
singleColumnLayout.marginHeight = 0;
singleColumnLayout.verticalSpacing = 0;
final GridLayout threeColumnLayout = new GridLayout(3, false);
threeColumnLayout.marginHeight = 0;
threeColumnLayout.verticalSpacing = 0;
// Add a group for the control widgets.
// As we don't want Jameica to make the widgets right-aligned and
// full-size, we put them into extra composites.
final Group group = new Group(getParent(), SWT.NONE);
group.setLayout(singleColumnLayout);
group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
// Add the settings buttons.
final Composite firstRowComposite = new Composite(group, SWT.NONE);
firstRowComposite.setLayout(singleColumnLayout);
final ButtonArea settingsButtons = new ButtonArea();
settingsButtons.addButton(JameicaPlugin.i18n().tr("Exchange Rates"), new ExchangeRates(), null, false, "invest.png");
settingsButtons.addButton(JameicaPlugin.i18n().tr("PayPal API Credentials"), new PayPalAPICredentials(), null, false, "seahorse-preferences.png");
settingsButtons.paint(firstRowComposite);
// Add the control widgets.
final Composite secondRowComposite = new Composite(group, SWT.NONE);
secondRowComposite.setLayout(threeColumnLayout);
secondRowComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
final GridData dateGridData = new GridData();
dateGridData.widthHint = 200;
final SimpleContainer startDateContainer = new SimpleContainer(secondRowComposite);
startDateContainer.getComposite().setLayoutData(dateGridData);
startDateInput = new DateInput(
yyyyMMddFormat.parse(settings.getString("startdate", "20121201")),
JameicaPlugin.dateFormat);
startDateContainer.addLabelPair(JameicaPlugin.i18n().tr("Start Date"), startDateInput);
final SimpleContainer endDateContainer = new SimpleContainer(secondRowComposite);
endDateContainer.getComposite().setLayoutData(dateGridData);
endDateInput = new DateInput(
yyyyMMddFormat.parse(settings.getString("enddate", "20121231")),
JameicaPlugin.dateFormat);
endDateContainer.addLabelPair(JameicaPlugin.i18n().tr("End Date"), endDateInput);
final Button downloadButton = new Button(
JameicaPlugin.i18n().tr("Download PayPal Transactions"), new Action()
{
@Override
public void handleAction(Object context) throws ApplicationException
{
// Save the entered dates.
final Date startDate = (Date) startDateInput.getValue();
final Date endDate = (Date) endDateInput.getValue();
settings.setAttribute("startdate", yyyyMMddFormat.format(startDate));
settings.setAttribute("enddate", yyyyMMddFormat.format(endDate));
// Open the Download dialog, which itself does the
// downloading.
try
{
new DownloadDialog(AbstractDialog.POSITION_CENTER, startDate,
endDate).open();
}
catch (OperationCanceledException e)
{
// Do nothing.
}
catch (Exception e)
{
Logger.error("Error during download", e);
}
}
}, null, true, "document-save.png");
downloadButton.paint(secondRowComposite);
// Add the tabs.
folder = new TabFolder(getParent(), SWT.NONE);
folder.setLayoutData(new GridData(GridData.FILL_BOTH));
for (int i = 0; i < payPalCurrencies.length; i++)
{
// Create the tab for this currency.
TabGroup tabGroup = new TabGroup(folder, "PayPal " + payPalCurrencies[i]);
tabs[i] = new TransactionTabControl(this);
tabs[i].add(tabGroup);
}
}
}

View File

@ -9,8 +9,7 @@ package org.reactos.ev.jameicaplugin.gui.view;
import de.willuhn.jameica.gui.AbstractView;
import de.willuhn.jameica.gui.GUI;
import de.willuhn.jameica.gui.internal.buttons.Back;
import de.willuhn.jameica.gui.util.ButtonArea;
import de.willuhn.jameica.gui.parts.ButtonArea;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
import org.reactos.ev.jameicaplugin.gui.action.HTMLOutput;
import org.reactos.ev.jameicaplugin.gui.action.NewAdditionalDonation;
@ -18,8 +17,9 @@ import org.reactos.ev.jameicaplugin.gui.control.DonationControl;
public class PublicDonationList extends AbstractView
{
DonationControl control = null;
DonationControl control;
@Override
public void bind() throws Exception
{
GUI.getView().setTitle(JameicaPlugin.i18n().tr("Public Donation List"));
@ -28,10 +28,10 @@ public class PublicDonationList extends AbstractView
control.getShowInvalidCheckbox().paint(this.getParent());
control.getDonationList().paint(this.getParent());
ButtonArea buttons = new ButtonArea(this.getParent(), 3);
buttons.addButton(new Back(false));
ButtonArea buttons = new ButtonArea();
buttons.addButton(JameicaPlugin.i18n().tr("HTML Output"), new HTMLOutput(), null, false, "text-html.png");
buttons.addButton(JameicaPlugin.i18n().tr("New additional donation"), new NewAdditionalDonation(), null, true, "document-new.png");
buttons.paint(this.getParent());
}
public DonationControl getDonationControl()

View File

@ -14,6 +14,7 @@ import org.reactos.ev.jameicaplugin.JameicaPlugin;
public class Welcome extends AbstractView
{
@Override
public void bind() throws Exception
{
GUI.getView().setTitle("ReactOS Deutschland e.V. Helper Plugin");

View File

@ -0,0 +1,224 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.io;
import de.jost_net.JVerein.Einstellungen;
import de.jost_net.JVerein.rmi.Buchung;
import de.jost_net.JVerein.rmi.Konto;
import de.willuhn.datasource.rmi.DBIterator;
import de.willuhn.datasource.rmi.DBService;
import de.willuhn.datasource.rmi.ResultSetExtractor;
import de.willuhn.util.ApplicationException;
import java.rmi.RemoteException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
public class JVereinIO
{
/** Map a PayPal currency code to a JVerein account ID. */
private final Map<String, String> currencyCodeToAccountID = new HashMap<String, String>();
/**
* All accounting types specifying donations (for looking up donations).
* Comma-separated in parentheses for a SQL "IN" statement.
*/
private String donationAccountingTypes;
/** Accounting type to use when adding a new donation. */
private Long newDonationAccountingType;
public JVereinIO()
{
// ReactOS Deutschland e.V. specific configuration!!
currencyCodeToAccountID.put("EUR", "2");
currencyCodeToAccountID.put("USD", "8");
donationAccountingTypes = "(1,2)";
newDonationAccountingType = 1L;
}
/**
* Extract the first column of the first row of a DB result as a double
* value.
*/
private class DoubleExtractor implements ResultSetExtractor
{
@Override
public Object extract(ResultSet rs) throws RemoteException, SQLException
{
if (!rs.next())
return new Double(0);
return new Double(rs.getDouble(1));
}
}
public Double getAccountTotal(String currencyCode) throws RemoteException
{
final String accountID = currencyCodeToAccountID.get(currencyCode);
String sql = "SELECT ";
// JVerein's entire accounting is in EUR currency.
// Therefore, we can only get the net amount directly for the EUR
// account.
// For non-EUR currencies, the net amount is entered at the
// beginning of the comment.
if (currencyCode.equals("EUR"))
sql += "SUM(betrag)";
else
sql += "SUM(CAST(kommentar AS DECIMAL(10,2)))";
sql += " FROM buchung WHERE konto = ?";
// Query the JVerein accounting information.
return (Double) Einstellungen.getDBService().execute(sql, new Object[]
{ accountID }, new DoubleExtractor());
}
public ArrayList<Transaction> getDonationTransactions(Date startDate, Date endDate,
String currencyCode) throws RemoteException
{
final String accountID = currencyCodeToAccountID.get(currencyCode);
// Query the JVerein accounting information.
final DBService service = Einstellungen.getDBService();
final DBIterator<Buchung> it = service.createList(Buchung.class);
it.addFilter("datum >= ? ", startDate);
it.addFilter("datum <= ? ", endDate);
it.addFilter("konto = ? ", accountID);
it.addFilter("buchungsart IN " + donationAccountingTypes);
// Build an ArrayList of transactions.
final ArrayList<Transaction> transactions = new ArrayList<Transaction>();
while (it.hasNext())
{
Buchung b = (Buchung) it.next();
String type = b.getArt();
String comment = b.getKommentar();
// The gross amount is always entered like "15 EUR" in the "Art"
// field of the JVerein transaction.
Double grossAmount = Double.parseDouble(type.substring(0, type.indexOf(" ")));
// JVerein's entire accounting is in EUR currency.
// Therefore, we can only get the net amount directly for the EUR
// account.
// For non-EUR currencies, the net amount is entered at the
// beginning of the comment.
Double netAmount;
if (currencyCode.equals("EUR"))
netAmount = b.getBetrag();
else
netAmount = Double.parseDouble(comment.substring(0, comment.indexOf(" ")));
Boolean anonymous = comment.contains("Anonym");
Transaction t = new Transaction(b.getID(), b.getDatum(), b.getName(), grossAmount,
netAmount, anonymous, comment, true, null);
transactions.add(t);
}
return transactions;
}
public Double getExchangeRateToEUR(Date date, String currencyCode) throws RemoteException
{
final Calendar cal = Calendar.getInstance();
cal.setTime(date);
final int year = cal.get(Calendar.YEAR);
final int month = cal.get(Calendar.MONTH) + 1;
final String sql = "SELECT exchange_rate_to_eur FROM exchange_rates "
+ "WHERE year = ? AND month = ? AND currency_code = ?";
return (Double) JameicaPlugin.getDBService().execute(sql, new Object[]
{ year, month, currencyCode }, new DoubleExtractor());
}
public void putDonationTransactions(ArrayList<Transaction> transactions, String currencyCode)
throws RemoteException
{
final String accountID = currencyCodeToAccountID.get(currencyCode);
// Query the account in the JVerein database.
final DBService service = Einstellungen.getDBService();
final DBIterator<Konto> it = service.createList(Konto.class);
it.addFilter("id = ?", accountID);
if (!it.hasNext())
throw new RemoteException("Unable to find the JVerein account");
Konto k = (Konto) it.next();
// Add all donations as JVerein transactions.
for (Transaction t : transactions)
{
if (!t.isDonation())
continue;
Buchung b = (Buchung) service.createObject(Buchung.class, null);
b.setKonto(k);
b.setName(t.getName());
// JVerein's entire accounting is in EUR currency.
// Therefore, we can only add EUR values directly as net amount.
// For all other currencies, we need to perform a currency
// conversion.
Double netAmount = t.getNetAmount();
Date date = t.getDate();
if (!currencyCode.equals("EUR"))
netAmount /= getExchangeRateToEUR(date, currencyCode);
b.setBetrag(netAmount);
b.setDatum(date);
// The gross amount and original currency is put in the "Art" field.
// This information is later used in the public donation list.
b.setArt(JameicaPlugin.currencyFormatUS.format(t.getGrossAmount()) + " "
+ currencyCode);
ArrayList<String> comments = new ArrayList<String>();
// When our transaction has a currency other than EUR, the net
// amount above will be the amount currency-converted to EUR.
// In this case, add the net amount in the original currency to the
// comments.
// This must always be the first entry in the comments for
// getAccountTotal to work!
if (!currencyCode.equals("EUR"))
{
comments.add(JameicaPlugin.currencyFormatUS.format(t.getNetAmount()) + " "
+ currencyCode + " nach Gebühren");
}
// Anonymous donations are also indicated in the comment.
if (t.isAnonymous())
comments.add("Anonym");
b.setKommentar(StringUtils.join(comments, "; "));
b.setBuchungsart(newDonationAccountingType);
try
{
b.store();
}
catch (ApplicationException e)
{
throw new RemoteException("Unable to store JVerein transaction", e);
}
}
}
}

View File

@ -0,0 +1,285 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.io;
import de.willuhn.jameica.gui.Action;
import de.willuhn.util.ApplicationException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.rmi.RemoteException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.time.DateUtils;
import org.reactos.ev.jameicaplugin.gui.dialog.PayPalAPICredentials;
public class PayPalIO
{
private final SimpleDateFormat isoDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
private final String payPalURL = "https://api-3t.paypal.com/nvp";
private String loginTemplate;
public PayPalIO()
{
// Prepare the Login string.
loginTemplate = "USER=" + PayPalAPICredentials.settings.getString("username", null);
loginTemplate += "&PWD=" + PayPalAPICredentials.settings.getString("password", null);
loginTemplate += "&SIGNATURE=" + PayPalAPICredentials.settings.getString("signature", null);
loginTemplate += "&VERSION=94";
}
private Map<String, String> doAPICall(String request) throws RemoteException
{
Map<String, String> responseMap = new HashMap<String, String>();
try
{
// Open a connection to the PayPal API.
URL url = new URL(payPalURL);
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
// Post our request.
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
writer.write(request);
writer.flush();
writer.close();
// Read the string response.
BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
StringBuilder response = new StringBuilder();
while ((line = reader.readLine()) != null)
{
response.append(line);
}
reader.close();
// Build the response map.
String[] parameters = response.toString().split("&");
for (String p : parameters)
{
String[] nameValue = p.split("=");
responseMap.put(nameValue[0], URLDecoder.decode(nameValue[1], "utf-8"));
}
}
catch (Exception e)
{
throw new RemoteException("Unable to perform PayPal API call", e);
}
return responseMap;
}
/**
* Attempts to figure out if the donor doesn't want his name to be
* published.
*
* @param comment
* The comment supplied with the PayPal transaction.
*
* @return true if the function detected anonymity, false otherwise.
*/
private Boolean isDonationAnonymous(String comment)
{
if (comment == null)
return false;
final String lowerCaseComment = comment.toLowerCase();
if (lowerCaseComment.contains("anonym"))
return true;
if (lowerCaseComment.contains("name"))
return true;
return false;
}
private void addPayPalTransaction(ArrayList<Transaction> transactions,
Map<String, String> responseMap, int i, String currencyCode) throws RemoteException
{
// Ignore any incomplete transactions.
if (!responseMap.get("L_STATUS" + i).equals("Completed"))
return;
// Even though we already filter by currency in the TransactionSearch
// call, some transactions with a different currency pass through.
// Filter them here again.
if (!responseMap.get("L_CURRENCYCODE" + i).equals(currencyCode))
return;
// Parse the ISO-8601 formatted date.
Date date;
try
{
date = isoDateFormat.parse(responseMap.get("L_TIMESTAMP" + i));
}
catch (ParseException e)
{
throw new RemoteException("Unable to parse the transaction date", e);
}
// Get our field information or make some guesses.
String type = responseMap.get("L_TYPE" + i);
Boolean isDonation = (type.equals("Donation") || type.equals("Recurring Payment"));
Boolean isAnonymous = false;
String id = responseMap.get("L_TRANSACTIONID" + i);
String name = responseMap.get("L_NAME" + i);
Double grossAmount = Double.parseDouble(responseMap.get("L_AMT" + i));
Double netAmount = Double.parseDouble(responseMap.get("L_NETAMT" + i));
String comment = null;
// Put "donations" in the other table, which are entirely eaten up by
// fees.
if (netAmount == 0.0)
isDonation = false;
// For donations, perform a GetTransactionDetails request to get the
// comment.
if (isDonation)
{
String request = loginTemplate;
request += "&METHOD=GetTransactionDetails";
request += "&TRANSACTIONID=" + id;
Map<String, String> detailsResponseMap = doAPICall(request);
// Check for success.
if (detailsResponseMap.get("ACK").equals("Success"))
{
// Get the comment and check if it indicates an anonymous
// donation.
comment = detailsResponseMap.get("NOTE");
isAnonymous = isDonationAnonymous(comment);
}
else
{
throw new RemoteException(
"GetTransactionDetails failed with: " + detailsResponseMap.get("ACK"));
}
}
Transaction t = new Transaction(id, date, name, grossAmount, netAmount, isAnonymous,
comment, isDonation, type);
transactions.add(t);
}
public Map<String, Double> getBalances() throws RemoteException
{
// Build a per-currency map for the balances.
Map<String, Double> balancesMap = new HashMap<String, Double>();
// Do the GetBalance API call.
String request = loginTemplate;
request += "&METHOD=GetBalance";
request += "&RETURNALLCURRENCIES=1";
Map<String, String> responseMap = doAPICall(request);
// Check for success.
if (responseMap.get("ACK").equals("Success"))
{
// Loop through all balances, break when no entry can be found in
// the map.
for (int i = 0; responseMap.get("L_CURRENCYCODE" + i) != null; i++)
{
String currencyCode = responseMap.get("L_CURRENCYCODE" + i);
Double balance = Double.parseDouble(responseMap.get("L_AMT" + i));
balancesMap.put(currencyCode, balance);
}
}
else
{
throw new RemoteException("GetBalance failed with: " + responseMap.get("ACK"));
}
return balancesMap;
}
public ArrayList<Transaction> getTransactions(Date startDate, Date endDate, String currencyCode,
Action progressAction) throws RemoteException
{
// Prepare the PayPal NVP API request.
String requestTemplate = loginTemplate;
requestTemplate += "&METHOD=TransactionSearch";
requestTemplate += "&CURRENCYCODE=" + currencyCode;
// Build an ArrayList of transactions.
ArrayList<Transaction> transactions = new ArrayList<Transaction>();
// For reporting progress.
int daysProcessed = 0;
int daysToProcess = (int) ((endDate.getTime() - startDate.getTime()) / 86400000) + 1;
// PayPal's API can only return a maximum of 100 transactions in a row.
// When there are more, it fails with ACK code "SuccessWithWarning".
// There is no documentation what to do to properly get all results and
// no duplicates.
// There is a StackOverflow discussion about this
// (http://stackoverflow.com/questions/16312839), but it's not even
// defined whether older or newer results are returned first.
//
// Therefore, I do a transaction search for every single day here.
// This also allows me to report progress.
while (!startDate.after(endDate))
{
Date iterationEndDate = DateUtils.addDays(startDate, 1);
// Do the TransactionSearch API call.
String request = requestTemplate;
request += "&STARTDATE=" + isoDateFormat.format(startDate);
request += "&ENDDATE=" + isoDateFormat.format(iterationEndDate);
Map<String, String> responseMap = doAPICall(request);
// Check for success.
if (responseMap.get("ACK").equals("Success"))
{
// Loop through all transactions, break when no entry can be
// found in the map.
for (int i = 0; responseMap.get("L_TIMESTAMP" + i) != null; i++)
{
addPayPalTransaction(transactions, responseMap, i, currencyCode);
}
}
else
{
throw new RemoteException(
"TransactionSearch failed with: " + responseMap.get("ACK"));
}
// Calculate and report progress.
daysProcessed++;
Integer percentage = 100 * daysProcessed / daysToProcess;
try
{
progressAction.handleAction(percentage);
}
catch (ApplicationException e)
{
throw new RemoteException("Unable to report progress", e);
}
// Move on to the next day.
startDate = iterationEndDate;
}
return transactions;
}
}

View File

@ -0,0 +1,184 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.io;
import static gcardone.junidecode.Junidecode.unidecode;
import de.willuhn.datasource.GenericObject;
import java.nio.charset.Charset;
import java.rmi.RemoteException;
import java.util.Date;
import org.apache.commons.lang.WordUtils;
public class Transaction implements GenericObject
{
private String id;
private Date date;
private String name;
private Double grossAmount;
private Double netAmount;
private Boolean anonymous;
private String comment;
private Boolean donation;
private String type;
public Transaction(String id, Date date, String name, Double grossAmount, Double netAmount,
Boolean anonymous, String comment, Boolean donation, String type)
{
this.id = id;
this.date = date;
setName(name);
this.grossAmount = grossAmount;
this.netAmount = netAmount;
this.anonymous = anonymous;
this.comment = comment;
this.donation = donation;
this.type = type;
}
@Override
public boolean equals(GenericObject arg0) throws RemoteException
{
if (arg0 == null || !(arg0 instanceof Transaction))
{
return false;
}
return this.getID().equals(arg0.getID());
}
@Override
public Object getAttribute(String arg0) throws RemoteException
{
if (arg0.equals("id"))
{
return id;
}
else if (arg0.equals("date"))
{
return date;
}
else if (arg0.equals("name"))
{
return name;
}
else if (arg0.equals("grossamount"))
{
return grossAmount;
}
else if (arg0.equals("netamount"))
{
return netAmount;
}
else if (arg0.equals("anonymous"))
{
return anonymous;
}
else if (arg0.equals("comment"))
{
return comment;
}
else if (arg0.equals("donation"))
{
return donation;
}
else if (arg0.equals("type"))
{
return type;
}
throw new RemoteException(String.format("Invalid attribute: %s", arg0));
}
@Override
public String[] getAttributeNames() throws RemoteException
{
return new String[]
{ "id", "date", "name", "grossamount", "netamount", "anonymous", "comment", "donation",
"type" };
}
@Override
public String getID() throws RemoteException
{
return id;
}
public Date getDate()
{
return date;
}
public String getName()
{
return name;
}
public Double getGrossAmount()
{
return grossAmount;
}
public Double getNetAmount()
{
return netAmount;
}
public Boolean isAnonymous()
{
return anonymous;
}
public String getComment()
{
return comment;
}
public Boolean isDonation()
{
return donation;
}
public String getType()
{
return type;
}
@Override
public String getPrimaryAttribute() throws RemoteException
{
return "id";
}
/**
* Converts the name into a readable ISO-8859-1 version.
*
* @param name
* The name for this transaction.
*/
public void setName(String name)
{
// Perform a transliteration if the name cannot be represented in
// ISO-8859-1.
if (!Charset.forName("ISO_8859_1").newEncoder().canEncode(name))
name = unidecode(name);
// Capitalize each word of the name.
this.name = WordUtils.capitalizeFully(name);
}
public void setAnonymous(Boolean anonymous)
{
this.anonymous = anonymous;
}
public void setDonation(Boolean donation)
{
this.donation = donation;
}
}

View File

@ -0,0 +1,30 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.rmi;
import de.willuhn.datasource.rmi.DBObject;
import java.rmi.RemoteException;
public interface ExchangeRate extends DBObject
{
public Integer getYear() throws RemoteException;
public Integer getMonth() throws RemoteException;
public String getCurrencyCode() throws RemoteException;
public Double getExchangeRateToEUR() throws RemoteException;
public void setYear(Integer year) throws RemoteException;
public void setMonth(Integer month) throws RemoteException;
public void setCurrencyCode(String currencyCode) throws RemoteException;
public void setExchangeRateToEUR(Double exchangeRateToEUR) throws RemoteException;
}

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -17,7 +17,7 @@ public interface JameicaPluginDBService extends DBService
{
public static final Settings settings = new Settings(JameicaPluginDBService.class);
public static final DBSupportMySqlImpl jvereinMySqlService = new DBSupportMySqlImpl();
public static final int ourVersion = 1;
public static final int ourVersion = 2;
/**
* Checks the version of the database and compares it with DBVersion.

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -9,6 +9,7 @@ package org.reactos.ev.jameicaplugin.server;
import de.jost_net.JVerein.server.Util;
import de.willuhn.datasource.db.AbstractDBObject;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
import java.rmi.RemoteException;
import java.util.Date;
@ -24,76 +25,91 @@ public class AdditionalDonationImpl extends AbstractDBObject implements Addition
super();
}
@Override
protected String getTableName()
{
return "additional_donations";
}
@Override
public String getPrimaryAttribute() throws RemoteException
{
return "id";
}
@Override
public Date getDate() throws RemoteException
{
return (Date) getAttribute("date");
}
@Override
public String getName() throws RemoteException
{
return (String) getAttribute("name");
}
@Override
public Boolean isAnonymous() throws RemoteException
{
return Util.getBoolean(getAttribute("anonymous"));
}
@Override
public Double getAmount() throws RemoteException
{
return (Double) getAttribute("amount");
}
@Override
public String getCurrency() throws RemoteException
{
return (String) getAttribute("currency");
}
@Override
public String getComment() throws RemoteException
{
return (String) getAttribute("comment");
}
@Override
public void setDate(Date date) throws RemoteException
{
setAttribute("date", date);
}
@Override
public void setName(String name) throws RemoteException
{
setAttribute("name", name);
}
@Override
public void setAnonymous(Boolean anonymous) throws RemoteException
{
setAttribute("anonymous", anonymous);
}
@Override
public void setAmount(Double amount) throws RemoteException
{
setAttribute("amount", amount);
}
@Override
public void setCurrency(String currency) throws RemoteException
{
setAttribute("currency", currency);
}
@Override
public void setComment(String comment) throws RemoteException
{
setAttribute("comment", comment);
}
@Override
protected void insertCheck() throws ApplicationException
{
try
@ -110,7 +126,7 @@ public class AdditionalDonationImpl extends AbstractDBObject implements Addition
}
catch (RemoteException e)
{
throw new ApplicationException("Error while checking the values!");
Logger.error("Error while checking the values", e);
}
}
}

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -22,46 +22,55 @@ public class DonationImpl extends AbstractDBObject implements Donation
super();
}
@Override
protected String getTableName()
{
return "all_donations";
}
@Override
public String getPrimaryAttribute() throws RemoteException
{
return "id";
}
@Override
public Long getJVereinID() throws RemoteException
{
return (Long) getAttribute("jverein_id");
}
@Override
public Date getDate() throws RemoteException
{
return (Date) getAttribute("date");
}
@Override
public String getName() throws RemoteException
{
return (String) getAttribute("name");
}
@Override
public Boolean isAnonymous() throws RemoteException
{
return Util.getBoolean(getAttribute("anonymous"));
}
@Override
public Double getAmount() throws RemoteException
{
return (Double) getAttribute("amount");
}
@Override
public String getCurrency() throws RemoteException
{
return (String) getAttribute("currency");
}
@Override
public String getComment() throws RemoteException
{
return (String) getAttribute("comment");

View File

@ -0,0 +1,119 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
package org.reactos.ev.jameicaplugin.server;
import de.willuhn.datasource.db.AbstractDBObject;
import de.willuhn.logging.Logger;
import de.willuhn.util.ApplicationException;
import java.rmi.RemoteException;
import org.reactos.ev.jameicaplugin.JameicaPlugin;
import org.reactos.ev.jameicaplugin.rmi.ExchangeRate;
public class ExchangeRateImpl extends AbstractDBObject implements ExchangeRate
{
private static final long serialVersionUID = 5198539453995722877L;
public ExchangeRateImpl() throws RemoteException
{
super();
}
@Override
protected String getTableName()
{
return "exchange_rates";
}
@Override
public String getPrimaryAttribute() throws RemoteException
{
return "id";
}
@Override
public Integer getYear() throws RemoteException
{
return (Integer) getAttribute("year");
}
@Override
public Integer getMonth() throws RemoteException
{
return (Integer) getAttribute("month");
}
@Override
public String getCurrencyCode() throws RemoteException
{
return (String) getAttribute("currency_code");
}
@Override
public Double getExchangeRateToEUR() throws RemoteException
{
return (Double) getAttribute("exchange_rate_to_eur");
}
@Override
public void setYear(Integer year) throws RemoteException
{
setAttribute("year", year);
}
@Override
public void setMonth(Integer month) throws RemoteException
{
setAttribute("month", month);
}
@Override
public void setCurrencyCode(String currencyCode) throws RemoteException
{
setAttribute("currency_code", currencyCode);
}
@Override
public void setExchangeRateToEUR(Double exchangeRateToEUR) throws RemoteException
{
setAttribute("exchange_rate_to_eur", exchangeRateToEUR);
}
@Override
protected void insertCheck() throws ApplicationException
{
try
{
// Check the values, support zero values for month and exchange rate
// though for the Add command.
if (getYear() < 2000 || getYear() > 2099)
{
throw new ApplicationException(
JameicaPlugin.i18n().tr("Please enter a valid year"));
}
else if (getMonth() < 0 || getMonth() > 12)
{
throw new ApplicationException(
JameicaPlugin.i18n().tr("Please enter a valid month"));
}
else if (getCurrencyCode() == null || getCurrencyCode().length() != 3)
{
throw new ApplicationException(
JameicaPlugin.i18n().tr("Please enter a 3-character currency code"));
}
else if (getExchangeRateToEUR() < 0.0)
{
throw new ApplicationException(
JameicaPlugin.i18n().tr("Please enter a valid exchange rate"));
}
}
catch (RemoteException e)
{
Logger.error("Error while checking the values", e);
}
}
}

View File

@ -1,7 +1,7 @@
/*
* PROJECT: ReactOS Deutschland e.V. Helper Plugin
* LICENSE: GNU GPL v2 or any later version as published by the Free Software Foundation
* COPYRIGHT: Copyright 2010 ReactOS Deutschland e.V. <deutschland@reactos.org>
* COPYRIGHT: Copyright 2010-2016 ReactOS Deutschland e.V. <deutschland@reactos.org>
* AUTHORS: Colin Finck <colin@reactos.org>
*/
@ -30,27 +30,38 @@ public class JameicaPluginDBServiceImpl extends DBServiceImpl implements Jameica
this.setClassFinder(Application.getClassLoader().getClassFinder());
}
@Override
public boolean getInsertWithID() throws RemoteException
{
return false;
}
@Override
protected String getJdbcDriver() throws RemoteException
{
return "com.mysql.jdbc.Driver";
}
@Override
protected String getJdbcPassword() throws RemoteException
{
return jvereinMySqlService.getJdbcPassword();
}
@Override
protected String getJdbcUrl() throws RemoteException
{
// JDBC URL includes database name, so we need our own setting here.
return settings.getString("jdbcurl", null);
}
@Override
protected String getJdbcUsername() throws RemoteException
{
return jvereinMySqlService.getJdbcUsername();
}
@Override
public void checkVersion() throws RemoteException, ApplicationException
{
try
@ -64,7 +75,8 @@ public class JameicaPluginDBServiceImpl extends DBServiceImpl implements Jameica
int dbVersion = rs.getInt(1);
if (ourVersion != dbVersion)
throw new ApplicationException("Version mismatch between program and database!");
throw new ApplicationException(
"Version mismatch between program and database!");
}
}
catch (SQLException e)
@ -73,9 +85,10 @@ public class JameicaPluginDBServiceImpl extends DBServiceImpl implements Jameica
}
}
@Override
public int getTransactionIsolationLevel()
{
// See database updates by others without issueing a COMMIT command.
// See database updates by others without issuing a COMMIT command.
// Needed to see entries changed in JVerein.
return Connection.TRANSACTION_READ_COMMITTED;
}