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 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.sales.pos.ui;

import org.openconcerto.erp.core.sales.pos.POSConfiguration;
import org.openconcerto.erp.core.sales.pos.io.BarcodeReader;
import org.openconcerto.erp.core.sales.pos.io.ConcertProtocol;
import org.openconcerto.erp.core.sales.pos.io.ESCSerialDisplay;
import org.openconcerto.erp.core.sales.pos.io.TicketPrinter;
import org.openconcerto.erp.core.sales.pos.model.Article;
import org.openconcerto.erp.core.sales.pos.model.Client;
import org.openconcerto.erp.core.sales.pos.model.Paiement;
import org.openconcerto.erp.core.sales.pos.model.ReceiptCode;
import org.openconcerto.erp.core.sales.pos.model.RegisterFiles;
import org.openconcerto.erp.core.sales.pos.model.RegisterLog;
import org.openconcerto.erp.core.sales.pos.model.RegisterLogEntry.ReceiptEntry;
import org.openconcerto.erp.core.sales.pos.model.RegisterState.Status;
import org.openconcerto.erp.core.sales.pos.model.Ticket;
import org.openconcerto.erp.core.sales.pos.model.TicketItem;
import org.openconcerto.erp.preferences.TemplateNXProps;
import org.openconcerto.sql.element.SQLElementDirectory;
import org.openconcerto.utils.FileUtils;
import org.openconcerto.utils.StringUtils;

import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JOptionPane;

import org.jdom2.JDOMException;

public class CaisseControler implements BarcodeListener {

    private Article articleSelected;
    private Paiement paiementSelected;
    private Ticket t;
    private Client client = Client.NONE;
    private List<CaisseListener> listeners = new ArrayList<>();

    private final BarcodeReader r;
    private Paiement p1 = new Paiement(Paiement.ESPECES);
    private Paiement p2 = new Paiement(Paiement.CB);
    private Paiement p3 = new Paiement(Paiement.CHEQUE);
    private final CaisseFrame caisseFrame;
    private final POSDisplay lcd;
    private TicketItem ticketItemSelected;

    public CaisseControler(CaisseFrame caisseFrame) throws ParseException, JDOMException, IOException {
        this.caisseFrame = caisseFrame;
        final RegisterLog lastLog = caisseFrame.getFiles().getLastLog();
        if (lastLog == null || lastLog.getRegisterState().getStatus() != Status.OPEN)
            throw new IllegalStateException("Not open");
        final String lastHash = lastLog.getLastReceiptHash();

        final ReceiptEntry lastReceiptEvent = lastLog.getLastReceiptCreationEvent();
        final int dayIndex;
        if (lastReceiptEvent == null) {
            dayIndex = 1;
        } else {
            final ReceiptCode lastCode = lastReceiptEvent.getCode();
            dayIndex = lastCode.getDayIndex() + 1;
        }
        final POSConfiguration posConf = getPOSConf();
        this.t = new Ticket(posConf.getPosID(), dayIndex, lastHash);

        this.t.addPaiement(this.p1);
        this.t.addPaiement(this.p2);
        this.t.addPaiement(this.p3);

        final int scanDelay = posConf.getScanDelay();
        this.r = new BarcodeReader(scanDelay);
        this.r.start();
        this.r.addBarcodeListener(this);
        if (posConf.getLCDType().equals("serial")) {
            this.lcd = new ESCSerialDisplay(posConf.getLCDPort());
        } else {
            this.lcd = new PrinterPOSDisplay(posConf.getLCDPort());
        }
        this.setLCDDefaultDisplay(0);
    }

    public final CaisseFrame getCaisseFrame() {
        return this.caisseFrame;
    }

    protected final POSConfiguration getPOSConf() {
        return getCaisseFrame().getPOSConf();
    }

    public Article getArticleSelected() {
        return this.articleSelected;
    }

    public Paiement getPaiementSelected() {
        return this.paiementSelected;
    }

    void setArticleSelected(Article a) {
        if (a != this.articleSelected) {
            this.articleSelected = a;
            this.paiementSelected = null;
            fire();
        }
    }

    void setPaiementSelected(Paiement p) {
        this.paiementSelected = p;
        this.articleSelected = null;
        this.ticketItemSelected = null;
        fire();
    }

    // Listeners
    private void fire() {
        int stop = this.listeners.size();
        for (int i = 0; i < stop; i++) {
            this.listeners.get(i).caisseStateChanged();
        }
    }

    void addCaisseListener(CaisseListener l) {
        this.listeners.add(l);
    }

    // Customer
    public void setClient(Client client) {
        if (client == null) {
            throw new IllegalArgumentException("Customer cannot be null");
        }
        this.client = client;
        this.t.setClient(client);
        fire();
    }

    public boolean isClientDefined() {
        return this.client != Client.NONE;
    }

    public Client getClient() {
        return this.client;
    }

    public void setCodePostal(String codePostal) {
        this.t.setCodePostal(codePostal);
    }

    // Articles
    public void addArticle(Article a) {
        this.t.addArticle(a);
        fire();
        String price = TicketCellRenderer.centsToString(a.getPriceWithTax().movePointRight(2).setScale(0, RoundingMode.HALF_UP).intValue());
        this.setLCD(a.getName(), price, 0);
        this.setLCDDefaultDisplay(2);
    }

    void incrementArticle(Article a) {
        this.t.incrementArticle(a);
        fire();
        String price = TicketCellRenderer.centsToString(a.getPriceWithTax().movePointRight(2).setScale(0, RoundingMode.HALF_UP).intValue());
        this.setLCD(a.getName(), price, 0);
        this.setLCDDefaultDisplay(2);
    }

    void removeArticle(Article a) {
        this.t.removeArticle(a);
        fire();
    }

    // Paiements
    public List<Paiement> getPaiements() {
        return this.t.getPaiements();
    }

    public void addPaiement(Paiement p) {
        this.t.addPaiement(p);
        fire();
    }

    public void clearPaiement(Paiement paiement) {
        if (this.p1.equals(paiement) || this.p2.equals(paiement) || this.p3.equals(paiement)) {
            paiement.setMontantInCents(0);
        }
        fire();
    }

    public void setPaiementValue(Paiement p, int v) {
        if (p.getType() == Paiement.SOLDE) {
            int soldeInCents = getClient().getSolde().movePointRight(2).intValue();
            if (v > soldeInCents) {
                v = soldeInCents;
            }
        }
        p.setMontantInCents(v);
        fire();
        this.setLCD("Paiement " + p.getTypeAsString().replace('è', 'e').replace('é', 'e'), TicketCellRenderer.centsToString(p.getMontantInCents()), 0);
        this.setLCDDefaultDisplay(3);
    }

    // Totaux
    public int getTotal() {
        return this.t.getTotalInCents();
    }

    public int getPaidTotal() {
        return this.t.getPaidTotal();
    }

    //

    public List<TicketItem> getItems() {
        return this.t.getItems();
    }

    public BigDecimal getItemCount(Article article) {
        return this.t.getItemCount(article);
    }

    public void clearArticle(Article article) {
        this.t.clearArticle(article);
        this.setTicketItemSelected(null);
    }

    public void setArticleCount(Article article, BigDecimal count) {
        this.t.setArticleCount(article, count);
        this.setArticleSelected(null);
    }

    @Override
    public void barcodeRead(String code) {
        System.err.println("CaisseControler.barcodeRead() barcode : " + code);
        if (code.equalsIgnoreCase("especes")) {
            autoFillPaiement(this.p1);

        } else if (code.equalsIgnoreCase("cb")) {
            autoFillPaiement(this.p2);

        } else if (code.equalsIgnoreCase("cheque")) {
            autoFillPaiement(this.p3);

        } else if (code.equalsIgnoreCase("annuler")) {
            if (this.articleSelected != null) {
                this.clearArticle(this.articleSelected);
            } else if (this.paiementSelected != null) {
                this.paiementSelected.setMontantInCents(0);
                fire();
            }
        } else if (code.equalsIgnoreCase("valider")) {

        } else if (code.equalsIgnoreCase("facture")) {

        } else if (code.equalsIgnoreCase("ticket")) {

        } else {
            Article a = Article.getArticleFromBarcode(code);
            if (a != null) {
                System.err.println("CaisseControler.barcodeRead() barcode : " + code + " : product found : " + a.getName());
                this.incrementArticle(a);
                this.setArticleSelected(a);
            } else {
                System.err.println("CaisseControler.barcodeRead() barcode : " + code + " : no product found");
                Ticket t = Ticket.getTicketFromCode(code, this.caisseFrame.getFiles());
                if (t != null) {
                    System.err.println("CaisseControler.barcodeRead() barcode : " + code + " : receipt found :" + t.getNumber());
                    this.caisseFrame.showTickets(t);
                }
            }
        }

    }

    void autoFillPaiement(Paiement p) {
        int nouveauMontant = getTotal() - getPaidTotal() + p.getMontantInCents();
        if (p.getType() == Paiement.SOLDE) {
            int soldeInCents = getClient().getSolde().movePointRight(2).intValue();
            if (nouveauMontant > soldeInCents) {
                nouveauMontant = soldeInCents;
            }
        }
        p.setMontantInCents(nouveauMontant);
        setPaiementSelected(p);
        this.setLCD("Paiement " + p.getTypeAsString(), TicketCellRenderer.centsToString(p.getMontantInCents()), 0);
        this.setLCDDefaultDisplay(3);
    }

    void addBarcodeListener(BarcodeListener l) {
        this.r.addBarcodeListener(l);
    }

    public boolean canAddPaiement(int type) {
        final int paiementCount = this.t.getPaiements().size();
        if (paiementCount >= 6)
            return false;
        for (int i = 0; i < paiementCount; i++) {
            Paiement p = this.t.getPaiements().get(i);
            if (p.getType() == type && p.getMontantInCents() != 0) {
                return false;
            }
        }

        return true;
    }

    @Override
    public void keyReceived(KeyEvent ee) {
        // nothing
    }

    public static String getCents(int cents) {
        String s = String.valueOf(Math.abs(cents) % 100);
        if (s.length() < 2) {
            s = "0" + s;
        }
        return s;
    }

    public static String getEuros(int cents) {
        return String.valueOf(cents / 100);
    }

    public Ticket saveAndClearTicket(final RegisterFiles files, final SQLElementDirectory dir) throws IOException, SQLException {
        if (!this.isTicketValid())
            return null;
        this.t.setCreationCal(Calendar.getInstance());
        final String fileHash = this.t.save(files, dir);
        final Ticket res = this.t;
        final int newIndex = this.t.getNumber() + 1;
        this.t = new Ticket(getPOSConf().getPosID(), newIndex, fileHash);
        this.p1 = new Paiement(Paiement.ESPECES);
        this.p2 = new Paiement(Paiement.CB);
        this.p3 = new Paiement(Paiement.CHEQUE);
        this.t.addPaiement(this.p1);
        this.t.addPaiement(this.p2);
        this.t.addPaiement(this.p3);
        this.setPaiementSelected(null);
        this.setArticleSelected(null);
        this.client = Client.NONE;
        return res;
    }

    public int getTicketNumber() {
        return this.t.getNumber();
    }

    public void openDrawer() {
        try {
            final TicketPrinter prt = getPOSConf().getTicketPrinterConfiguration1().createTicketPrinter();
            prt.openDrawer();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void switchListMode() {
        this.caisseFrame.mainPanel.switchListMode();

    }

    public void setLCD(final String line1, final String line2, final int delay) {
        final TimerTask task = new TimerTask() {
            @Override
            public void run() {
                try {
                    CaisseControler.this.lcd.setMessage(line1, line2);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        final Timer timer = new Timer("LCD : " + line1, true);
        timer.schedule(task, delay * 1000L);

    }

    public void setLCDDefaultDisplay(int delay) {
        if (this.t.getTotalInCents() > 0) {
            int count = 0;
            final List<TicketItem> articles = this.t.getItems();
            for (TicketItem pair : articles) {
                Article a = pair.getArticle();
                if (a.getSalesUnit() == null) {
                    count += pair.getQty().intValue();
                } else {
                    count++;
                }
            }
            String line1;
            if (count == 1) {
                line1 = "1 article";
            } else {
                line1 = count + " articles";
            }
            int cents = this.t.getTotalInCents();
            setLCD(line1, "Total : " + TicketCellRenderer.centsToString(cents), delay);
        } else {
            setLCD(getPOSConf().getLCDLine1(), getPOSConf().getLCDLine2(), delay);
        }
    }

    public void sendCBRequest(final Paiement p) {

        final String creditCardPort = getPOSConf().getCreditCardPort();
        if (creditCardPort != null && creditCardPort.trim().length() > 2) {
            final Thread thread = new Thread(new Runnable() {

                @Override
                public void run() {
                    try {
                        ConcertProtocol cp = new ConcertProtocol(creditCardPort);
                        boolean ok = cp.sendCardPayment(p.getMontantInCents(), ConcertProtocol.CURRENCY_EUR);
                        if (ok) {
                            JOptionPane.showMessageDialog(null, "Paiement CB OK");
                        } else {
                            JOptionPane.showMessageDialog(null, "Erreur paiement CB");
                        }
                    } catch (Throwable ex) {
                        JOptionPane.showMessageDialog(null, "Erreur terminal CB");
                    }
                }
            });
            thread.setDaemon(true);
            thread.start();

        }

    }

    public void setArticleHT(TicketItem item, BigDecimal ht) {
        final Article newArticle = new Article(item.getArticle());
        newArticle.updatePriceWithoutTax(ht);
        item.setArticle(newArticle);
        fire();
    }

    public void openPriceEditor(TicketItem item) {
        this.caisseFrame.showPriceEditor(item, this);
    }

    public void enableBarcodeReader() {
        this.r.setEnabled(true);
    }

    public void disableBarcodeReader() {
        this.r.setEnabled(false);
    }

    public boolean isTicketValid() {
        return (!this.t.getItems().isEmpty()) && ((this.getTotal() >= 0 && this.getPaidTotal() >= this.getTotal()) || (this.getTotal() < 0 && this.getPaidTotal() == this.getTotal()));
    }

    public Set<Integer> loadFavoriteProductsIds() {
        final TemplateNXProps nxprops = (TemplateNXProps) TemplateNXProps.getInstance();
        final File f = new File(nxprops.getDefaultStringValue(), "favorites.txt");
        System.out.println("CaisseControler.saveFavoriteProductsIds() loading favorites from " + f.getAbsolutePath());
        final Set<Integer> result = new HashSet<>();
        if (f.exists()) {
            try {
                String s = FileUtils.read(f);
                List<String> sIds = StringUtils.fastSplit(s, ',');
                for (String string : sIds) {
                    if (!string.isEmpty()) {
                        result.add(Integer.parseInt(string));
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }

    public void saveFavoriteProductsIds(List<Article> products) {
        final TemplateNXProps nxprops = (TemplateNXProps) TemplateNXProps.getInstance();
        final File f = new File(nxprops.getDefaultStringValue(), "favorites.txt");
        System.out.println("CaisseControler.saveFavoriteProductsIds() saving favorites to " + f.getAbsolutePath());
        try (FileOutputStream fOut = new FileOutputStream(f);) {
            for (Article product : products) {
                fOut.write(String.valueOf(product.getId()).getBytes());
                fOut.write(',');
            }
            fOut.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void setTicketItemSelected(TicketItem item) {
        this.ticketItemSelected = item;
        if (item == null) {
            this.articleSelected = null;
        } else {
            this.articleSelected = item.getArticle();
            this.paiementSelected = null;
        }
        fire();

    }

    public TicketItem getTicketItemSelected() {
        return this.ticketItemSelected;
    }

    public void removeTicketItem(TicketItem item) {
        this.t.removeTicketItem(item);
        this.setTicketItemSelected(null);
    }

    public void cancel(Ticket ticket) {
        this.t.clear();
        // Annulation du ticket
        for (TicketItem a : ticket.getItems()) {
            final Article article = a.getArticle();
            TicketItem item = new TicketItem(article, a.getQty().multiply(new BigDecimal(-1)));
            this.t.addItem(item);
        }
        for (Paiement p : ticket.getPaiements()) {
            final Paiement paiement = new Paiement(p.getType());
            paiement.setMontantInCents(-1 * p.getMontantInCents());
            this.t.addPaiement(paiement);
        }

        this.caisseFrame.showCaisse();
        fire();
    }

}