Dépôt officiel du code source de l'ERP OpenConcerto
Rev 156 | Rev 180 | Go to most recent revision | 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();
}
}