OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 180 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
18 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
18 ilm 5
 *
6
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
7
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
8
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.erp.core.sales.pos.ui;
15
 
132 ilm 16
import org.openconcerto.erp.core.sales.pos.POSConfiguration;
18 ilm 17
import org.openconcerto.erp.core.sales.pos.io.BarcodeReader;
132 ilm 18
import org.openconcerto.erp.core.sales.pos.io.ESCSerialDisplay;
18 ilm 19
import org.openconcerto.erp.core.sales.pos.io.TicketPrinter;
20
import org.openconcerto.erp.core.sales.pos.model.Article;
142 ilm 21
import org.openconcerto.erp.core.sales.pos.model.Client;
18 ilm 22
import org.openconcerto.erp.core.sales.pos.model.Paiement;
144 ilm 23
import org.openconcerto.erp.core.sales.pos.model.ReceiptCode;
24
import org.openconcerto.erp.core.sales.pos.model.RegisterFiles;
25
import org.openconcerto.erp.core.sales.pos.model.RegisterLog;
26
import org.openconcerto.erp.core.sales.pos.model.RegisterLogEntry.ReceiptEntry;
27
import org.openconcerto.erp.core.sales.pos.model.RegisterState.Status;
18 ilm 28
import org.openconcerto.erp.core.sales.pos.model.Ticket;
174 ilm 29
import org.openconcerto.erp.core.sales.pos.model.TicketItem;
149 ilm 30
import org.openconcerto.erp.preferences.TemplateNXProps;
182 ilm 31
import org.openconcerto.erp.utils.SoundGenerator;
144 ilm 32
import org.openconcerto.sql.element.SQLElementDirectory;
149 ilm 33
import org.openconcerto.utils.FileUtils;
34
import org.openconcerto.utils.StringUtils;
18 ilm 35
 
36
import java.awt.event.KeyEvent;
149 ilm 37
import java.io.File;
38
import java.io.FileOutputStream;
144 ilm 39
import java.io.IOException;
142 ilm 40
import java.math.BigDecimal;
132 ilm 41
import java.math.RoundingMode;
144 ilm 42
import java.sql.SQLException;
43
import java.text.ParseException;
18 ilm 44
import java.util.ArrayList;
93 ilm 45
import java.util.Calendar;
149 ilm 46
import java.util.HashSet;
18 ilm 47
import java.util.List;
182 ilm 48
import java.util.Map;
149 ilm 49
import java.util.Set;
132 ilm 50
import java.util.Timer;
51
import java.util.TimerTask;
18 ilm 52
 
144 ilm 53
import org.jdom2.JDOMException;
54
 
18 ilm 55
public class CaisseControler implements BarcodeListener {
56
 
57
    private Article articleSelected;
58
    private Paiement paiementSelected;
59
    private Ticket t;
142 ilm 60
    private Client client = Client.NONE;
174 ilm 61
    private List<CaisseListener> listeners = new ArrayList<>();
18 ilm 62
 
132 ilm 63
    private final BarcodeReader r;
18 ilm 64
    private Paiement p1 = new Paiement(Paiement.ESPECES);
65
    private Paiement p2 = new Paiement(Paiement.CB);
66
    private Paiement p3 = new Paiement(Paiement.CHEQUE);
132 ilm 67
    private final CaisseFrame caisseFrame;
142 ilm 68
    private final POSDisplay lcd;
174 ilm 69
    private TicketItem ticketItemSelected;
18 ilm 70
 
144 ilm 71
    public CaisseControler(CaisseFrame caisseFrame) throws ParseException, JDOMException, IOException {
18 ilm 72
        this.caisseFrame = caisseFrame;
144 ilm 73
        final RegisterLog lastLog = caisseFrame.getFiles().getLastLog();
74
        if (lastLog == null || lastLog.getRegisterState().getStatus() != Status.OPEN)
75
            throw new IllegalStateException("Not open");
76
        final String lastHash = lastLog.getLastReceiptHash();
18 ilm 77
 
144 ilm 78
        final ReceiptEntry lastReceiptEvent = lastLog.getLastReceiptCreationEvent();
79
        final int dayIndex;
80
        if (lastReceiptEvent == null) {
81
            dayIndex = 1;
82
        } else {
83
            final ReceiptCode lastCode = lastReceiptEvent.getCode();
84
            dayIndex = lastCode.getDayIndex() + 1;
85
        }
156 ilm 86
        final POSConfiguration posConf = getPOSConf();
87
        this.t = new Ticket(posConf.getPosID(), dayIndex, lastHash);
144 ilm 88
 
18 ilm 89
        this.t.addPaiement(this.p1);
90
        this.t.addPaiement(this.p2);
91
        this.t.addPaiement(this.p3);
92
 
156 ilm 93
        final int scanDelay = posConf.getScanDelay();
144 ilm 94
        this.r = new BarcodeReader(scanDelay);
18 ilm 95
        this.r.start();
96
        this.r.addBarcodeListener(this);
156 ilm 97
        if (posConf.getLCDType().equals("serial")) {
174 ilm 98
            this.lcd = new ESCSerialDisplay(posConf.getLCDPort());
142 ilm 99
        } else {
174 ilm 100
            this.lcd = new PrinterPOSDisplay(posConf.getLCDPort());
142 ilm 101
        }
132 ilm 102
        this.setLCDDefaultDisplay(0);
18 ilm 103
    }
104
 
149 ilm 105
    public final CaisseFrame getCaisseFrame() {
106
        return this.caisseFrame;
107
    }
108
 
156 ilm 109
    protected final POSConfiguration getPOSConf() {
110
        return getCaisseFrame().getPOSConf();
111
    }
112
 
18 ilm 113
    public Article getArticleSelected() {
114
        return this.articleSelected;
115
    }
116
 
117
    public Paiement getPaiementSelected() {
118
        return this.paiementSelected;
119
    }
120
 
121
    void setArticleSelected(Article a) {
174 ilm 122
        if (a != this.articleSelected) {
65 ilm 123
            this.articleSelected = a;
124
            this.paiementSelected = null;
125
            fire();
126
        }
18 ilm 127
    }
128
 
129
    void setPaiementSelected(Paiement p) {
130
        this.paiementSelected = p;
131
        this.articleSelected = null;
174 ilm 132
        this.ticketItemSelected = null;
18 ilm 133
        fire();
134
    }
135
 
136
    // Listeners
137
    private void fire() {
138
        int stop = this.listeners.size();
139
        for (int i = 0; i < stop; i++) {
140
            this.listeners.get(i).caisseStateChanged();
141
        }
142
    }
143
 
144
    void addCaisseListener(CaisseListener l) {
145
        this.listeners.add(l);
146
    }
147
 
142 ilm 148
    // Customer
149
    public void setClient(Client client) {
150
        if (client == null) {
151
            throw new IllegalArgumentException("Customer cannot be null");
152
        }
153
        this.client = client;
154
        this.t.setClient(client);
155
        fire();
156
    }
157
 
158
    public boolean isClientDefined() {
159
        return this.client != Client.NONE;
160
    }
161
 
162
    public Client getClient() {
174 ilm 163
        return this.client;
142 ilm 164
    }
165
 
174 ilm 166
    public void setCodePostal(String codePostal) {
167
        this.t.setCodePostal(codePostal);
168
    }
169
 
18 ilm 170
    // Articles
151 ilm 171
    public void addArticle(Article a) {
18 ilm 172
        this.t.addArticle(a);
173
        fire();
182 ilm 174
 
175
        String price = TicketCellRenderer.centsToString(a.getPriceWithTax(this.t.getItemCount(a), false).movePointRight(2).setScale(0, RoundingMode.HALF_UP).intValue());
132 ilm 176
        this.setLCD(a.getName(), price, 0);
177
        this.setLCDDefaultDisplay(2);
18 ilm 178
    }
179
 
180
    void incrementArticle(Article a) {
181
        this.t.incrementArticle(a);
182
        fire();
182 ilm 183
 
184
        String price = TicketCellRenderer.centsToString(a.getPriceWithTax(this.t.getItemCount(a), false).movePointRight(2).setScale(0, RoundingMode.HALF_UP).intValue());
132 ilm 185
        this.setLCD(a.getName(), price, 0);
186
        this.setLCDDefaultDisplay(2);
18 ilm 187
    }
188
 
189
    void removeArticle(Article a) {
190
        this.t.removeArticle(a);
191
        fire();
192
    }
193
 
194
    // Paiements
195
    public List<Paiement> getPaiements() {
196
        return this.t.getPaiements();
197
    }
198
 
199
    public void addPaiement(Paiement p) {
200
        this.t.addPaiement(p);
201
        fire();
202
    }
203
 
204
    public void clearPaiement(Paiement paiement) {
205
        if (this.p1.equals(paiement) || this.p2.equals(paiement) || this.p3.equals(paiement)) {
206
            paiement.setMontantInCents(0);
207
        }
208
        fire();
209
    }
210
 
132 ilm 211
    public void setPaiementValue(Paiement p, int v) {
142 ilm 212
        if (p.getType() == Paiement.SOLDE) {
213
            int soldeInCents = getClient().getSolde().movePointRight(2).intValue();
214
            if (v > soldeInCents) {
215
                v = soldeInCents;
216
            }
217
        }
132 ilm 218
        p.setMontantInCents(v);
18 ilm 219
        fire();
132 ilm 220
        this.setLCD("Paiement " + p.getTypeAsString().replace('è', 'e').replace('é', 'e'), TicketCellRenderer.centsToString(p.getMontantInCents()), 0);
221
        this.setLCDDefaultDisplay(3);
18 ilm 222
    }
223
 
224
    // Totaux
225
    public int getTotal() {
83 ilm 226
        return this.t.getTotalInCents();
18 ilm 227
    }
228
 
229
    public int getPaidTotal() {
230
        return this.t.getPaidTotal();
231
    }
232
 
233
    //
234
 
174 ilm 235
    public List<TicketItem> getItems() {
236
        return this.t.getItems();
18 ilm 237
    }
238
 
174 ilm 239
    public BigDecimal getItemCount(Article article) {
18 ilm 240
        return this.t.getItemCount(article);
241
    }
242
 
243
    public void clearArticle(Article article) {
244
        this.t.clearArticle(article);
174 ilm 245
        this.setTicketItemSelected(null);
18 ilm 246
    }
247
 
174 ilm 248
    public void setArticleCount(Article article, BigDecimal count) {
18 ilm 249
        this.t.setArticleCount(article, count);
250
        this.setArticleSelected(null);
251
    }
252
 
253
    @Override
254
    public void barcodeRead(String code) {
144 ilm 255
        System.err.println("CaisseControler.barcodeRead() barcode : " + code);
18 ilm 256
        if (code.equalsIgnoreCase("especes")) {
257
            autoFillPaiement(this.p1);
258
 
259
        } else if (code.equalsIgnoreCase("cb")) {
260
            autoFillPaiement(this.p2);
261
 
262
        } else if (code.equalsIgnoreCase("cheque")) {
263
            autoFillPaiement(this.p3);
264
 
265
        } else if (code.equalsIgnoreCase("annuler")) {
266
            if (this.articleSelected != null) {
267
                this.clearArticle(this.articleSelected);
174 ilm 268
            } else if (this.paiementSelected != null) {
269
                this.paiementSelected.setMontantInCents(0);
270
                fire();
18 ilm 271
            }
272
        } else if (code.equalsIgnoreCase("valider")) {
273
 
274
        } else if (code.equalsIgnoreCase("facture")) {
275
 
276
        } else if (code.equalsIgnoreCase("ticket")) {
277
 
278
        } else {
182 ilm 279
            List<Article> list = Article.getArticleFromBarcode(code);
280
            if (list != null && !list.isEmpty()) {
281
                if (list.size() == 1) {
282
                    final Article a = list.get(0);
283
                    System.err.println("CaisseControler.barcodeRead() barcode : " + code + " : product found : " + a.getName());
284
                    this.incrementArticle(a);
285
                    this.setArticleSelected(a);
286
                } else {
287
                    // choix de l'article
288
                    caisseFrame.showArticleSelector(list, new ArticleSelectionListener() {
289
                        public void articleSelected(Article a) {
290
                            System.err.println("CaisseControler.barcodeRead() barcode : " + code + " : product selected : " + a.getName());
291
                            incrementArticle(a);
292
                            setArticleSelected(a);
293
                            caisseFrame.showCaisse();
294
                        }
295
 
296
                    });
297
                }
80 ilm 298
            } else {
144 ilm 299
                System.err.println("CaisseControler.barcodeRead() barcode : " + code + " : no product found");
300
                Ticket t = Ticket.getTicketFromCode(code, this.caisseFrame.getFiles());
83 ilm 301
                if (t != null) {
144 ilm 302
                    System.err.println("CaisseControler.barcodeRead() barcode : " + code + " : receipt found :" + t.getNumber());
174 ilm 303
                    this.caisseFrame.showTickets(t);
182 ilm 304
 
305
                } else {
306
                    // Joue un son si rien n'est trouvé
307
                    new SoundGenerator().playSound(300.0, 0.15, 0.75, SoundGenerator.FADE_NONE, SoundGenerator.WAVE_SQUARE);
308
 
83 ilm 309
                }
18 ilm 310
            }
311
        }
312
 
313
    }
314
 
315
    void autoFillPaiement(Paiement p) {
142 ilm 316
        int nouveauMontant = getTotal() - getPaidTotal() + p.getMontantInCents();
317
        if (p.getType() == Paiement.SOLDE) {
318
            int soldeInCents = getClient().getSolde().movePointRight(2).intValue();
319
            if (nouveauMontant > soldeInCents) {
320
                nouveauMontant = soldeInCents;
321
            }
322
        }
323
        p.setMontantInCents(nouveauMontant);
18 ilm 324
        setPaiementSelected(p);
132 ilm 325
        this.setLCD("Paiement " + p.getTypeAsString(), TicketCellRenderer.centsToString(p.getMontantInCents()), 0);
326
        this.setLCDDefaultDisplay(3);
18 ilm 327
    }
328
 
329
    void addBarcodeListener(BarcodeListener l) {
330
        this.r.addBarcodeListener(l);
331
    }
332
 
333
    public boolean canAddPaiement(int type) {
334
        final int paiementCount = this.t.getPaiements().size();
335
        if (paiementCount >= 6)
336
            return false;
337
        for (int i = 0; i < paiementCount; i++) {
338
            Paiement p = this.t.getPaiements().get(i);
151 ilm 339
            if (p.getType() == type && p.getMontantInCents() != 0) {
18 ilm 340
                return false;
341
            }
342
        }
343
 
344
        return true;
345
    }
346
 
347
    @Override
348
    public void keyReceived(KeyEvent ee) {
174 ilm 349
        // nothing
18 ilm 350
    }
351
 
352
    public static String getCents(int cents) {
151 ilm 353
        String s = String.valueOf(Math.abs(cents) % 100);
18 ilm 354
        if (s.length() < 2) {
355
            s = "0" + s;
356
        }
357
        return s;
358
    }
359
 
360
    public static String getEuros(int cents) {
174 ilm 361
        return String.valueOf(cents / 100);
18 ilm 362
    }
363
 
149 ilm 364
    public Ticket saveAndClearTicket(final RegisterFiles files, final SQLElementDirectory dir) throws IOException, SQLException {
365
        if (!this.isTicketValid())
366
            return null;
144 ilm 367
        this.t.setCreationCal(Calendar.getInstance());
149 ilm 368
        final String fileHash = this.t.save(files, dir);
369
        final Ticket res = this.t;
370
        final int newIndex = this.t.getNumber() + 1;
174 ilm 371
        this.t = new Ticket(getPOSConf().getPosID(), newIndex, fileHash);
372
        this.p1 = new Paiement(Paiement.ESPECES);
373
        this.p2 = new Paiement(Paiement.CB);
374
        this.p3 = new Paiement(Paiement.CHEQUE);
149 ilm 375
        this.t.addPaiement(this.p1);
376
        this.t.addPaiement(this.p2);
377
        this.t.addPaiement(this.p3);
378
        this.setPaiementSelected(null);
379
        this.setArticleSelected(null);
174 ilm 380
        this.client = Client.NONE;
149 ilm 381
        return res;
144 ilm 382
    }
383
 
18 ilm 384
    public int getTicketNumber() {
385
        return this.t.getNumber();
386
    }
387
 
388
    public void openDrawer() {
389
        try {
156 ilm 390
            final TicketPrinter prt = getPOSConf().getTicketPrinterConfiguration1().createTicketPrinter();
18 ilm 391
            prt.openDrawer();
392
        } catch (Exception e) {
132 ilm 393
            e.printStackTrace();
18 ilm 394
        }
395
 
396
    }
83 ilm 397
 
398
    public void switchListMode() {
174 ilm 399
        this.caisseFrame.mainPanel.switchListMode();
83 ilm 400
 
401
    }
132 ilm 402
 
403
    public void setLCD(final String line1, final String line2, final int delay) {
404
        final TimerTask task = new TimerTask() {
405
            @Override
406
            public void run() {
407
                try {
174 ilm 408
                    CaisseControler.this.lcd.setMessage(line1, line2);
132 ilm 409
                } catch (Exception e) {
410
                    e.printStackTrace();
411
                }
412
            }
413
        };
414
        final Timer timer = new Timer("LCD : " + line1, true);
174 ilm 415
        timer.schedule(task, delay * 1000L);
132 ilm 416
 
417
    }
418
 
419
    public void setLCDDefaultDisplay(int delay) {
174 ilm 420
        if (this.t.getTotalInCents() > 0) {
132 ilm 421
            int count = 0;
174 ilm 422
            final List<TicketItem> articles = this.t.getItems();
423
            for (TicketItem pair : articles) {
424
                Article a = pair.getArticle();
425
                if (a.getSalesUnit() == null) {
426
                    count += pair.getQty().intValue();
427
                } else {
428
                    count++;
429
                }
132 ilm 430
            }
431
            String line1;
432
            if (count == 1) {
433
                line1 = "1 article";
434
            } else {
435
                line1 = count + " articles";
436
            }
174 ilm 437
            int cents = this.t.getTotalInCents();
132 ilm 438
            setLCD(line1, "Total : " + TicketCellRenderer.centsToString(cents), delay);
439
        } else {
156 ilm 440
            setLCD(getPOSConf().getLCDLine1(), getPOSConf().getLCDLine2(), delay);
132 ilm 441
        }
442
    }
443
 
444
    public void sendCBRequest(final Paiement p) {
182 ilm 445
        caisseFrame.showCBPanel(p);
132 ilm 446
    }
142 ilm 447
 
174 ilm 448
    public void setArticleHT(TicketItem item, BigDecimal ht) {
449
        final Article newArticle = new Article(item.getArticle());
450
        newArticle.updatePriceWithoutTax(ht);
451
        item.setArticle(newArticle);
452
        fire();
142 ilm 453
    }
454
 
174 ilm 455
    public void openPriceEditor(TicketItem item) {
456
        this.caisseFrame.showPriceEditor(item, this);
142 ilm 457
    }
458
 
459
    public void enableBarcodeReader() {
460
        this.r.setEnabled(true);
461
    }
462
 
463
    public void disableBarcodeReader() {
464
        this.r.setEnabled(false);
465
    }
144 ilm 466
 
467
    public boolean isTicketValid() {
174 ilm 468
        return (!this.t.getItems().isEmpty()) && ((this.getTotal() >= 0 && this.getPaidTotal() >= this.getTotal()) || (this.getTotal() < 0 && this.getPaidTotal() == this.getTotal()));
144 ilm 469
    }
149 ilm 470
 
182 ilm 471
 
149 ilm 472
 
174 ilm 473
    public void setTicketItemSelected(TicketItem item) {
474
        this.ticketItemSelected = item;
475
        if (item == null) {
476
            this.articleSelected = null;
477
        } else {
478
            this.articleSelected = item.getArticle();
479
            this.paiementSelected = null;
480
        }
481
        fire();
482
 
483
    }
484
 
485
    public TicketItem getTicketItemSelected() {
486
        return this.ticketItemSelected;
487
    }
488
 
489
    public void removeTicketItem(TicketItem item) {
490
        this.t.removeTicketItem(item);
491
        this.setTicketItemSelected(null);
492
    }
493
 
494
    public void cancel(Ticket ticket) {
495
        this.t.clear();
496
        // Annulation du ticket
497
        for (TicketItem a : ticket.getItems()) {
498
            final Article article = a.getArticle();
499
            TicketItem item = new TicketItem(article, a.getQty().multiply(new BigDecimal(-1)));
500
            this.t.addItem(item);
501
        }
180 ilm 502
        // Annulation de chaque paiement
503
        final List<Paiement> typesAdded = new ArrayList<>();
174 ilm 504
        for (Paiement p : ticket.getPaiements()) {
505
            final Paiement paiement = new Paiement(p.getType());
506
            paiement.setMontantInCents(-1 * p.getMontantInCents());
507
            this.t.addPaiement(paiement);
180 ilm 508
            typesAdded.add(p);
174 ilm 509
        }
180 ilm 510
        // On complete avec les autres types
511
        final List<Paiement> types = new ArrayList<>();
512
        types.add(new Paiement(Paiement.CB));
513
        types.add(new Paiement(Paiement.CHEQUE));
514
        types.add(new Paiement(Paiement.ESPECES));
515
        for (Paiement paiement : types) {
516
            boolean typeFound = false;
517
            for (Paiement p : typesAdded) {
518
                if (paiement.getType() == p.getType()) {
519
                    typeFound = true;
520
                }
521
            }
522
            if (!typeFound) {
523
                this.t.addPaiement(paiement);
524
            }
525
        }
174 ilm 526
 
527
        this.caisseFrame.showCaisse();
528
        fire();
529
    }
530
 
182 ilm 531
    /**
532
     * Ouvre la fenetre d'alerte de stock
533
     *
534
     * @param runnable a executer en cas de transfert de stock ou de bypass
535
     *
536
     * @param map TicketItem <-> qté manquante
537
     */
538
    public void openStockErrorPanel(Map<TicketItem, Integer> missingQty, Runnable runnable) {
539
        this.caisseFrame.showStockErrorPanel(missingQty, runnable);
540
 
541
    }
542
 
18 ilm 543
}