OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 156 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.erp.core.finance.accounting.model;

import org.openconcerto.erp.config.ComptaPropsConfiguration;
import org.openconcerto.sql.model.DBRoot;
import org.openconcerto.sql.model.Order;
import org.openconcerto.sql.model.SQLRow;
import org.openconcerto.sql.model.SQLRowListRSH;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.Where;
import org.openconcerto.utils.DecimalUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.Tuple3;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

public class CurrencyConverter {
    private String companyCurrencyCode;
    private final DBRoot root;
    private boolean useCache = false;
    private String baseCurrencyCode;

    public CurrencyConverter(DBRoot rootSociete, String companyCurrencyCode, String baseCurrencyCode) {
        if (companyCurrencyCode == null || companyCurrencyCode.isEmpty()) {
            this.companyCurrencyCode = "EUR";
        } else {
            this.companyCurrencyCode = companyCurrencyCode.trim().toUpperCase();
        }
        this.baseCurrencyCode = baseCurrencyCode;
        this.root = rootSociete;
    }

    public CurrencyConverter() {
        this(ComptaPropsConfiguration.getInstanceCompta().getRootSociete(), ComptaPropsConfiguration.getInstanceCompta().getCurrency().getCode(), "EUR");
    }

    public String getCompanyCurrencyCode() {
        return companyCurrencyCode;
    }

    public String getBaseCurrencyCode() {
        return baseCurrencyCode;
    }

    public void setUseCache(boolean useCache) {
        this.useCache = useCache;
    }

    /**
     * Converter an amount to an other currency
     */
    public BigDecimal convert(BigDecimal amount, String from, String to) {
        return convert(amount, from, to, Calendar.getInstance().getTime());
    }

    /**
     * Converter an amount to an other currency at a precise date
     */
    public BigDecimal convert(BigDecimal amount, String from, String to, Date date) {
        return convert(amount, from, to, date, false);
    }

    Map<Tuple3<String, String, Date>, Tuple2<BigDecimal, BigDecimal>> cacheBiased = new HashMap<Tuple3<String, String, Date>, Tuple2<BigDecimal, BigDecimal>>();
    Map<Tuple3<String, String, Date>, Tuple2<BigDecimal, BigDecimal>> cacheNotBiased = new HashMap<Tuple3<String, String, Date>, Tuple2<BigDecimal, BigDecimal>>();

    public void clearCache() {
        cacheBiased.clear();
        cacheNotBiased.clear();
    }

    /**
     * Converter an amount to an other currency at a precise date
     */
    public BigDecimal convert(BigDecimal amount, String from, String to, Date date, boolean useBiased) {

        if (from.equalsIgnoreCase(to)) {
            return amount;
        }

        if (amount.signum() == 0) {
            return BigDecimal.ZERO;
        }

        BigDecimal r1 = null;
        BigDecimal r2 = null;

        if (useCache) {
            Tuple3<String, String, Date> key = Tuple3.create(from, to, date);
            if (useBiased && cacheBiased.containsKey(key)) {
                Tuple2<BigDecimal, BigDecimal> value = cacheBiased.get(key);
                r1 = value.get0();
                r2 = value.get1();
            } else if (!useBiased && cacheNotBiased.containsKey(key)) {
                Tuple2<BigDecimal, BigDecimal> value = cacheNotBiased.get(key);
                r1 = value.get0();
                r2 = value.get1();
            }
        }

        if (r1 == null || r2 == null) {
            // Clean date
            final Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
            c.setTimeInMillis(date.getTime());
            c.set(Calendar.HOUR_OF_DAY, 23);
            c.set(Calendar.MINUTE, 59);
            c.set(Calendar.SECOND, 59);
            c.set(Calendar.MILLISECOND, 59);

            // Limit pour récupérer le taux de la veille pour le taux réel
            if (!useBiased) {
                c.add(Calendar.DAY_OF_MONTH, -1);
            }

            final Date d = c.getTime();

            // Get conversion info
            final List<SQLRow> rowsFrom = getRates(from, d);
            final List<SQLRow> rowsTo = getRates(to, d);

            List<SQLRow> rows = new ArrayList<SQLRow>();
            rows.addAll(rowsTo);
            rows.addAll(rowsFrom);
            // Pour le taux réel, on récupére celui de la veille
            for (SQLRow sqlRow : rows) {
                if (sqlRow.getString("DST").equals(from)) {
                    if (useBiased) {
                        if (r1 == null) {
                            r1 = sqlRow.getBigDecimal("TAUX_COMMERCIAL");
                        }
                    } else {
                        r1 = sqlRow.getBigDecimal("TAUX");
                    }
                }
                if (sqlRow.getString("DST").equals(to)) {
                    if (useBiased) {
                        if (r2 == null) {
                            r2 = sqlRow.getBigDecimal("TAUX_COMMERCIAL");
                        }
                    } else {
                        r2 = sqlRow.getBigDecimal("TAUX");
                    }
                }
            }
            if (from.equals(this.baseCurrencyCode)) {
                r1 = BigDecimal.ONE;
            }
            if (to.equals(this.baseCurrencyCode)) {
                r2 = BigDecimal.ONE;
            }

            if (r1 == null || r2 == null) {
                // Récupération des taux par défaut dans DEVISE si aucun taux dans l'historique
                List<SQLRow> rowsDevise = new ArrayList<SQLRow>();
                if (rowsTo.isEmpty() || rowsFrom.isEmpty()) {
                    SQLSelect sel = new SQLSelect();
                    sel.addSelectStar(root.findTable("DEVISE"));
                    rowsDevise.addAll(SQLRowListRSH.execute(sel));
                }
                if (r2 == null) {
                    for (SQLRow sqlRow : rowsDevise) {
                        if (sqlRow.getString("CODE").equalsIgnoreCase(to)) {
                            r2 = (useBiased ? sqlRow.getBigDecimal("TAUX_COMMERCIAL") : sqlRow.getBigDecimal("TAUX"));
                        }
                    }
                }
                if (r1 == null) {
                    for (SQLRow sqlRow : rowsDevise) {
                        if (sqlRow.getString("CODE").equalsIgnoreCase(from)) {
                            r1 = (useBiased ? sqlRow.getBigDecimal("TAUX_COMMERCIAL") : sqlRow.getBigDecimal("TAUX"));
                        }
                    }
                }
            }

        }
        if (r1 == null) {
            throw new IllegalStateException("No conversion rate for " + from);
        }
        if (r2 == null) {
            throw new IllegalStateException("No conversion rate for " + to);
        }
        if (useCache) {
            Tuple3<String, String, Date> key = Tuple3.create(from, to, date);

            if (useBiased) {
                cacheBiased.put(key, Tuple2.create(r1, r2));
            } else {
                cacheNotBiased.put(key, Tuple2.create(r1, r2));
            }
        }
        if (r1.equals(BigDecimal.ZERO)) {
            r1 = BigDecimal.ONE;
        }
        final BigDecimal result = amount.multiply(r2, DecimalUtils.HIGH_PRECISION).divide(r1, DecimalUtils.HIGH_PRECISION);
        return result;
    }

    public List<SQLRow> getRates(String currencyCode, final Date d) {
        final SQLSelect select = new SQLSelect();
        final SQLTable t = this.root.getTable("DEVISE_HISTORIQUE");
        select.addAllSelect(t, Arrays.asList("ID", "DATE", "SRC", "DST", "TAUX", "TAUX_COMMERCIAL"));
        Where w = new Where(t.getField("SRC"), "=", baseCurrencyCode);
        w = w.and(new Where(t.getField("DST"), "=", currencyCode));
        w = w.and(new Where(t.getField("DATE"), "<=", d));
        select.setWhere(w);
        select.addFieldOrder(t.getField("DATE"), Order.desc());
        // On ne veut que le plus récent
        select.setLimit(1);
        final List<SQLRow> rows = SQLRowListRSH.execute(select);
        return rows;
    }
}