Dépôt officiel du code source de l'ERP OpenConcerto
Rev 182 | 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.sales.pos.ui;
import org.openconcerto.erp.core.sales.pos.POSConfiguration;
import org.openconcerto.erp.core.sales.pos.io.Barcode;
import org.openconcerto.erp.core.sales.pos.io.BarcodeListener;
import org.openconcerto.erp.core.sales.pos.io.BarcodeReader;
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.erp.utils.SoundGenerator;
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.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
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(this.t.getItemCount(a), false).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(this.t.getItemCount(a), false).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(Barcode barcode) {
String code = barcode.getData();
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 {
List<Article> list = Article.getArticleFromBarcode(code);
if (list != null && !list.isEmpty()) {
if (list.size() == 1) {
final Article a = list.get(0);
System.err.println("CaisseControler.barcodeRead() barcode : " + code + " : product found : " + a.getName());
this.incrementArticle(a);
this.setArticleSelected(a);
} else {
// choix de l'article
caisseFrame.showArticleSelector(list, new ArticleSelectionListener() {
public void articleSelected(Article a) {
System.err.println("CaisseControler.barcodeRead() barcode : " + code + " : product selected : " + a.getName());
incrementArticle(a);
setArticleSelected(a);
caisseFrame.showCaisse();
}
});
}
} 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);
} else {
// Joue un son si rien n'est trouvé
new SoundGenerator().playSound(300.0, 0.15, 0.75, SoundGenerator.FADE_NONE, SoundGenerator.WAVE_SQUARE);
}
}
}
}
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) {
caisseFrame.showCBPanel(p);
}
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 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);
}
// Annulation de chaque paiement
final List<Paiement> typesAdded = new ArrayList<>();
for (Paiement p : ticket.getPaiements()) {
final Paiement paiement = new Paiement(p.getType());
paiement.setMontantInCents(-1 * p.getMontantInCents());
this.t.addPaiement(paiement);
typesAdded.add(p);
}
// On complete avec les autres types
final List<Paiement> types = new ArrayList<>();
types.add(new Paiement(Paiement.CB));
types.add(new Paiement(Paiement.CHEQUE));
types.add(new Paiement(Paiement.ESPECES));
for (Paiement paiement : types) {
boolean typeFound = false;
for (Paiement p : typesAdded) {
if (paiement.getType() == p.getType()) {
typeFound = true;
}
}
if (!typeFound) {
this.t.addPaiement(paiement);
}
}
this.caisseFrame.showCaisse();
fire();
}
/**
* Ouvre la fenetre d'alerte de stock
*
* @param runnable a executer en cas de transfert de stock ou de bypass
*
* @param map TicketItem <-> qté manquante
*/
public void openStockErrorPanel(Map<TicketItem, Integer> missingQty, Runnable runnable) {
this.caisseFrame.showStockErrorPanel(missingQty, runnable);
}
}