OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 174 | 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
 *
4
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
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
 
16
import org.openconcerto.erp.core.sales.pos.model.Article;
17
import org.openconcerto.erp.core.sales.pos.model.Categorie;
149 ilm 18
import org.openconcerto.erp.core.sales.pos.model.RegisterFiles.DifferentDayException;
19
import org.openconcerto.erp.core.sales.pos.model.Ticket;
18 ilm 20
import org.openconcerto.sql.element.SQLElement;
144 ilm 21
import org.openconcerto.sql.element.SQLElementDirectory;
18 ilm 22
import org.openconcerto.sql.model.SQLRow;
174 ilm 23
import org.openconcerto.sql.model.SQLRowAccessor;
18 ilm 24
import org.openconcerto.sql.model.SQLRowListRSH;
174 ilm 25
import org.openconcerto.sql.model.SQLRowValues;
26
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
18 ilm 27
import org.openconcerto.sql.model.SQLSelect;
142 ilm 28
import org.openconcerto.sql.model.SQLTable;
144 ilm 29
import org.openconcerto.sql.model.SQLTable.VirtualFields;
142 ilm 30
import org.openconcerto.sql.model.Where;
19 ilm 31
import org.openconcerto.utils.ExceptionHandler;
144 ilm 32
import org.openconcerto.utils.StringUtils;
18 ilm 33
 
34
import java.awt.Color;
35
import java.awt.Font;
36
import java.awt.Graphics;
37
import java.awt.Graphics2D;
38
import java.awt.GridBagConstraints;
39
import java.awt.GridBagLayout;
142 ilm 40
import java.awt.Insets;
18 ilm 41
import java.awt.RenderingHints;
142 ilm 42
import java.awt.event.ActionEvent;
43
import java.awt.event.ActionListener;
18 ilm 44
import java.awt.geom.Rectangle2D;
142 ilm 45
import java.text.DecimalFormat;
149 ilm 46
import java.util.ArrayList;
144 ilm 47
import java.util.Arrays;
18 ilm 48
import java.util.HashMap;
149 ilm 49
import java.util.List;
18 ilm 50
import java.util.Map;
149 ilm 51
import java.util.Set;
18 ilm 52
 
83 ilm 53
import javax.swing.JOptionPane;
18 ilm 54
import javax.swing.JPanel;
55
 
56
public class CaissePanel extends JPanel implements CaisseListener {
142 ilm 57
 
58
    public static final Color LIGHT_BLUE = new Color(83, 129, 172);
59
    public static final Color DARK_BLUE = new Color(0, 98, 159);
60
 
18 ilm 61
    private CaisseControler controler;
25 ilm 62
 
18 ilm 63
    private StatusBar st;
83 ilm 64
    private ArticleSelectorPanel articleSelectorPanel;
65
    private ArticleSearchPanel articleSearchPanel;
18 ilm 66
 
174 ilm 67
    private JPanel selector;
83 ilm 68
 
144 ilm 69
    public CaissePanel(final CaisseFrame caisseFrame) throws Exception {
70
        final SQLElementDirectory dir = caisseFrame.getConf().getDirectory();
71
 
18 ilm 72
        this.setLayout(new GridBagLayout());
73
        this.setBackground(Color.WHITE);
74
        this.setOpaque(isOpaque());
75
        GridBagConstraints c = new GridBagConstraints();
174 ilm 76
 
77
        // Column 1
18 ilm 78
        c.gridx = 0;
79
        c.gridy = 0;
83 ilm 80
        c.weightx = 0;
18 ilm 81
        c.weighty = 0;
82
        this.controler = new CaisseControler(caisseFrame);
83
        c.fill = GridBagConstraints.HORIZONTAL;
142 ilm 84
        this.st = createStatusBar(caisseFrame);
18 ilm 85
        this.add(this.st, c);
86
 
87
        TicketPanel t = new TicketPanel(this.controler);
144 ilm 88
        loadArticles(dir);
18 ilm 89
        c.gridy++;
90
        c.weighty = 1;
91
        c.gridwidth = 1;
92
        c.anchor = GridBagConstraints.SOUTHWEST;
93
        c.fill = GridBagConstraints.NONE;
94
        this.add(t, c);
174 ilm 95
        // Column 2
18 ilm 96
        c.fill = GridBagConstraints.BOTH;
97
        c.gridx++;
174 ilm 98
        c.gridy = 0;
18 ilm 99
        c.weightx = 1;
100
        c.gridheight = 2;
174 ilm 101
        this.articleSelectorPanel = new ArticleSelectorPanel(this.controler);
102
        this.articleSearchPanel = new ArticleSearchPanel(this.controler);
103
        this.selector = this.articleSelectorPanel;
104
        this.add(this.selector, c);
105
        // Column 3
18 ilm 106
        c.gridx++;
107
        c.weightx = 0;
177 ilm 108
        this.add(new PaiementPanel(this), c);
18 ilm 109
        this.controler.addCaisseListener(this);
110
    }
111
 
142 ilm 112
    private StatusBar createStatusBar(final CaisseFrame caisseFrame) {
113
        final StatusBar s = new StatusBar();
114
        s.setLayout(new GridBagLayout());
115
        final GridBagConstraints c = new GridBagConstraints();
174 ilm 116
        if (this.controler.getPOSConf().getScreenWidth() < 1280) {
117
            c.insets = new Insets(0, 2, 0, 2);
118
        } else {
119
            c.insets = new Insets(0, 10, 0, 10);
120
        }
142 ilm 121
        c.gridx = 0;
122
        c.gridy = 0;
123
        c.fill = GridBagConstraints.NONE;
124
        c.anchor = GridBagConstraints.CENTER;
125
        c.weightx = 0;
126
        final POSButton bValidate = new POSButton("Valider");
127
        bValidate.setForeground(Color.WHITE);
128
        bValidate.setBackground(DARK_BLUE);
129
        s.add(bValidate, c);
130
        c.weightx = 1;
131
        c.gridx++;
132
        final POSButton bClients = new POSButton("Clients");
133
        bClients.setForeground(Color.WHITE);
134
        bClients.setBackground(DARK_BLUE);
135
        s.add(bClients, c);
136
        c.gridx++;
137
        final POSButton bMenu = new POSButton("Menu");
138
        bMenu.setForeground(Color.WHITE);
139
        bMenu.setBackground(DARK_BLUE);
140
        s.add(bMenu, c);
141
        bValidate.addActionListener(new ActionListener() {
142
 
143
            @Override
144
            public void actionPerformed(ActionEvent e) {
149 ilm 145
                final Ticket savedReceipt;
146
                try {
147
                    savedReceipt = CaissePanel.this.controler.saveAndClearTicket(caisseFrame.getFiles(), caisseFrame.getConf().getDirectory());
148
                } catch (DifferentDayException ex) {
149
                    JOptionPane.showMessageDialog(CaissePanel.this, "Impossible de laisser la caisse ouverte plusieurs jours. Veuillez la clôturer pour pouvoir faire de nouveaux tickets.", "Erreur",
150
                            JOptionPane.ERROR_MESSAGE);
151
                    return;
152
                } catch (Throwable ex) {
153
                    ExceptionHandler.handle(CaissePanel.this, "Erreur de sauvegarde des informations du ticket", ex);
154
                    return;
155
                }
156
                if (savedReceipt != null) {
144 ilm 157
                    // Valider
158
                    CaissePanel.this.controler.setLCD("Impression de", "votre ticket...", 0);
159
                    try {
156 ilm 160
                        caisseFrame.getPOSConf().print(savedReceipt, (savedReceipt.isAdditionnalCopyRequested() ? 1 : 0));
144 ilm 161
                    } catch (UnsatisfiedLinkError ex) {
162
                        JOptionPane.showMessageDialog(CaissePanel.this, "Erreur de configuration de la liaison à l'imprimante");
163
                    } catch (Throwable ex) {
149 ilm 164
                        ex.printStackTrace();
144 ilm 165
                        JOptionPane.showMessageDialog(CaissePanel.this, "Erreur d'impression du ticket");
166
                    }
149 ilm 167
 
144 ilm 168
                    CaissePanel.this.controler.setLCDDefaultDisplay(2);
142 ilm 169
                }
149 ilm 170
 
142 ilm 171
            }
172
        });
173
        bClients.addActionListener(new ActionListener() {
174
 
175
            @Override
176
            public void actionPerformed(ActionEvent e) {
177
                // Clients
178
                try {
179
                    caisseFrame.showClients();
180
                } catch (Throwable ex) {
181
                    ExceptionHandler.handle("Erreur d'affichage du menu", ex);
182
                }
183
 
184
            }
185
        });
186
 
187
        bMenu.addActionListener(new ActionListener() {
188
 
189
            @Override
190
            public void actionPerformed(ActionEvent e) {
191
                // Menu
192
                try {
193
                    caisseFrame.showMenu();
194
                } catch (Throwable ex) {
195
                    ExceptionHandler.handle("Erreur d'affichage du menu", ex);
196
                }
197
 
198
            }
199
        });
200
 
201
        return s;
202
    }
203
 
144 ilm 204
    private void loadArticles(final SQLElementDirectory dir) {
174 ilm 205
        final SQLSelect selUniteVente = new SQLSelect();
206
        selUniteVente.addSelectStar(dir.getElement("UNITE_VENTE").getTable());
207
        final Map<Integer, String> mapUniteVenteName = new HashMap<>();
208
        for (SQLRow row : SQLRowListRSH.execute(selUniteVente)) {
209
            mapUniteVenteName.put(row.getID(), row.getString("CODE"));
210
        }
18 ilm 211
 
174 ilm 212
        final Set<Integer> favoriteProductsIds = this.controler.loadFavoriteProductsIds();
149 ilm 213
        final List<Article> favoriteProducts = new ArrayList<>();
214
 
174 ilm 215
        final Map<Integer, Categorie> categoriesMap = new HashMap<>();
18 ilm 216
 
144 ilm 217
        SQLElement eltFam = dir.getElement("FAMILLE_ARTICLE");
218
        SQLElement eltArticle = dir.getElement("ARTICLE");
18 ilm 219
 
144 ilm 220
        final SQLSelect selFamille = new SQLSelect();
18 ilm 221
        selFamille.addSelectStar(eltFam.getTable());
144 ilm 222
        selFamille.addFieldOrder(eltFam.getTable().getField("CODE"));
149 ilm 223
 
144 ilm 224
        for (SQLRow row : SQLRowListRSH.execute(selFamille)) {
83 ilm 225
            // Map id -> Category
226
            final Categorie cP = categoriesMap.get(row.getInt("ID_FAMILLE_ARTICLE_PERE"));
18 ilm 227
            Categorie c;
228
            if (cP != null) {
229
                c = new Categorie(row.getString("NOM"));
230
                cP.add(c);
231
            } else {
232
                c = new Categorie(row.getString("NOM"), true);
233
            }
234
 
83 ilm 235
            categoriesMap.put(row.getID(), c);
18 ilm 236
        }
237
 
83 ilm 238
        final SQLSelect selArticle = new SQLSelect();
142 ilm 239
        final SQLTable tableArticle = eltArticle.getTable();
144 ilm 240
        selArticle.addAllSelect(tableArticle.getFields(VirtualFields.PRIMARY_KEY.union(VirtualFields.ARCHIVE)));
174 ilm 241
        selArticle.addAllSelect(tableArticle, Arrays.asList("ID_FAMILLE_ARTICLE", "NOM", "CODE", "CODE_BARRE", "ID_TAXE", "PV_HT", "PV_TTC", "ADDITIONAL_TICKET_COPY", "ID_UNITE_VENTE"));
149 ilm 242
        selArticle.setWhere(new Where(tableArticle.getField("OBSOLETE"), "=", Boolean.FALSE).and(new Where(tableArticle.getField("MASQUE_CAISSE"), "=", Boolean.FALSE)));
18 ilm 243
 
80 ilm 244
        final Categorie cUnclassified = new Categorie("Non classés", true);
149 ilm 245
        cUnclassified.setUnknown();
144 ilm 246
        for (SQLRow row : SQLRowListRSH.execute(selArticle)) {
247
            final int idFamilleArticle = row.getInt("ID_FAMILLE_ARTICLE");
248
            Categorie s1 = categoriesMap.get(idFamilleArticle);
80 ilm 249
            if (s1 == null) {
250
                s1 = cUnclassified;
144 ilm 251
                categoriesMap.put(idFamilleArticle, cUnclassified);
80 ilm 252
            }
253
            final String name = row.getString("NOM").trim();
254
            if (name.length() > 0) {
255
                final Article a = new Article(s1, name, row.getID());
83 ilm 256
                final String barcode = row.getString("CODE_BARRE");
144 ilm 257
                final String code = row.getString("CODE");
258
                a.setBarCode(StringUtils.isEmpty(barcode, true) ? code : barcode);
259
                a.setCode(code);
18 ilm 260
                a.setIdTaxe(row.getInt("ID_TAXE"));
144 ilm 261
                a.setPriceWithoutTax(row.getBigDecimal("PV_HT"));
262
                a.setPriceWithTax(row.getBigDecimal("PV_TTC"));
151 ilm 263
                a.setAdditionalCopyRequested(row.getBoolean("ADDITIONAL_TICKET_COPY"));
174 ilm 264
                if (row.getInt("ID_UNITE_VENTE") != 2) {
265
                    a.setSalesUnit(mapUniteVenteName.get(row.getInt("ID_UNITE_VENTE")));
266
                }
149 ilm 267
                final Integer idProduct = a.getId();
268
                if (favoriteProductsIds.contains(idProduct)) {
269
                    favoriteProducts.add(a);
270
                }
271
 
18 ilm 272
            }
273
        }
149 ilm 274
        Categorie.setFavoriteProducts(favoriteProducts);
174 ilm 275
        initCacheArticleMap(dir);
18 ilm 276
    }
277
 
174 ilm 278
    private static Map<Integer, SQLRowAccessor> cacheArticle = new HashMap<>();
279
 
280
    public static SQLRowAccessor getArticleRowValuesFromCache(int id) {
281
        return cacheArticle.get(id);
282
    }
283
 
284
    private void initCacheArticleMap(final SQLElementDirectory dir) {
285
        final SQLTable tableArt = dir.getElement("ARTICLE").getTable();
286
        SQLRowValues rowValsArt = new SQLRowValues(tableArt);
287
        rowValsArt.putNulls(tableArt.getFieldsName());
288
        final SQLTable tableArtCatComptable = dir.getElement("ARTICLE_CATEGORIE_COMPTABLE").getTable();
289
        SQLRowValues rowValsArtCatComptable = new SQLRowValues(tableArtCatComptable);
290
        rowValsArtCatComptable.putNulls(tableArtCatComptable.getFieldsName());
291
        rowValsArtCatComptable.put("ID_ARTICLE", rowValsArt);
292
        final SQLRowValues rowValsCaCompt = rowValsArtCatComptable.putRowValues("ID_CATEGORIE_COMPTABLE");
293
        rowValsCaCompt.putNulls(rowValsCaCompt.getTable().getFieldsName());
294
 
295
        final SQLRowValues rowValsFam = rowValsArt.putRowValues("ID_FAMILLE_ARTICLE");
296
        rowValsFam.putNulls(rowValsFam.getTable().getFieldsName());
297
        final SQLRowValues rowValuesFamP = rowValsFam.putRowValues("ID_FAMILLE_ARTICLE_PERE");
298
        rowValuesFamP.putNulls(rowValuesFamP.getTable().getFieldsName());
299
 
300
        SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(rowValsArt);
301
        List<SQLRowValues> res = fetcher.fetch();
302
        for (SQLRowValues sqlRowValues : res) {
303
            cacheArticle.put(sqlRowValues.getID(), sqlRowValues);
304
        }
305
 
306
    }
307
 
18 ilm 308
    @Override
309
    public void paint(Graphics g) {
67 ilm 310
        System.err.println("CaissePanel.paint()" + this.getWidth() + " x " + this.getHeight());
18 ilm 311
        super.paint(g);
312
        Graphics2D g2 = (Graphics2D) g;
313
        g.setFont(new Font("Arial", Font.PLAIN, 32));
314
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
142 ilm 315
 
316
        if (this.controler.isClientDefined()) {
317
            g.setColor(CaissePanel.DARK_BLUE);
318
            g.setFont(new Font("Arial", Font.PLAIN, 28));
319
            g.drawString(this.controler.getClient().getFullName(), 20, 75);
320
            g.setColor(Color.GRAY);
321
            g.setFont(g.getFont().deriveFont(18f));
322
            g.drawString("Solde : " + new DecimalFormat("#0.00").format(this.controler.getClient().getSolde()), 20, 120);
323
 
324
        }
174 ilm 325
        int xPos = 300;
326
        if (this.controler.getPOSConf().getScreenWidth() < 1280) {
327
            xPos = 270;
328
        }
18 ilm 329
        // Prix
174 ilm 330
        int x = xPos;
18 ilm 331
        int y = 110;
332
        String euros;
333
        String cents;
334
        Rectangle2D r;
142 ilm 335
        g.setColor(Color.BLACK);
336
        if (this.controler.isClientDefined()) {
337
            g.setFont(g.getFont().deriveFont(46f));
338
            y += 10;
339
        } else {
340
            g.setFont(g.getFont().deriveFont(66f));
341
        }
18 ilm 342
        final int total = this.controler.getTotal();
25 ilm 343
        euros = CaisseControler.getEuros(total) + ".";
344
        cents = CaisseControler.getCents(total);
18 ilm 345
        r = g.getFontMetrics().getStringBounds(euros, g);
346
        x = x - (int) r.getWidth();
347
        g.drawString(euros, x, y);
348
        g.setFont(g.getFont().deriveFont(40f));
349
        g.drawString(cents, x + (int) r.getWidth(), y);
350
        // Paiement
351
        y += 40;
174 ilm 352
        x = xPos;
18 ilm 353
        final int paye = this.controler.getPaidTotal();
25 ilm 354
        euros = CaisseControler.getEuros(paye) + ".";
355
        cents = CaisseControler.getCents(paye);
18 ilm 356
 
357
        g.setFont(g.getFont().deriveFont(18f));
358
        Rectangle2D r2 = g.getFontMetrics().getStringBounds("Payé", g);
359
        if (paye >= total) {
360
            g.setColor(Color.DARK_GRAY);
361
        } else {
362
            g.setColor(Color.ORANGE);
363
        }
364
        g.setFont(g.getFont().deriveFont(32f));
365
        r = g.getFontMetrics().getStringBounds(euros, g);
366
        g.drawString(euros, x - (int) r.getWidth(), y);
367
        g.setFont(g.getFont().deriveFont(24f));
368
        g.drawString(cents, x, y);
369
        g.setFont(g.getFont().deriveFont(18f));
370
        g.setColor(Color.GRAY);
371
        g.drawString("Payé", x - (int) r2.getWidth() - (int) r.getWidth() - 10, y);
372
        // A rendre
67 ilm 373
        final boolean minimalHeight = this.getHeight() < 750;
374
        if (!minimalHeight) {
375
            y += 40;
174 ilm 376
            x = xPos;
67 ilm 377
        } else {
378
            x = 140;
379
        }
18 ilm 380
        int aRendre = paye - total;
381
        if (aRendre != 0) {
382
            String label;
383
            if (aRendre > 0) {
384
                label = "Rendu";
385
            } else {
67 ilm 386
                if (!minimalHeight) {
387
                    label = "Reste à payer";
388
                } else {
389
                    label = "Doit";
390
                }
18 ilm 391
                aRendre = -aRendre;
392
            }
393
 
25 ilm 394
            euros = CaisseControler.getEuros(aRendre) + ".";
395
            cents = CaisseControler.getCents(aRendre);
18 ilm 396
 
397
            g.setFont(g.getFont().deriveFont(18f));
398
            Rectangle2D r3 = g.getFontMetrics().getStringBounds(label, g);
399
 
400
            g.setColor(Color.DARK_GRAY);
401
            g.setFont(g.getFont().deriveFont(32f));
402
            r = g.getFontMetrics().getStringBounds(euros, g);
403
            g.drawString(euros, x - (int) r.getWidth(), y);
404
            g.setFont(g.getFont().deriveFont(24f));
405
            g.drawString(cents, x, y);
406
            g.setFont(g.getFont().deriveFont(18f));
407
            g.setColor(Color.GRAY);
408
            g.drawString(label, x - (int) r3.getWidth() - (int) r.getWidth() - 10, y);
409
 
410
        }
411
 
412
    }
413
 
414
    @Override
415
    public void caisseStateChanged() {
416
        repaint();
417
    }
83 ilm 418
 
419
    public void switchListMode() {
420
 
174 ilm 421
        GridBagConstraints c = ((GridBagLayout) this.getLayout()).getConstraints(this.selector);
422
        this.remove(this.selector);
83 ilm 423
 
174 ilm 424
        if (this.selector == this.articleSearchPanel) {
425
            this.selector = this.articleSelectorPanel;
83 ilm 426
        } else {
174 ilm 427
            this.selector = this.articleSearchPanel;
83 ilm 428
        }
174 ilm 429
        System.err.println("CaissePanel.switchListMode()" + this.selector.getMinimumSize() + " " + this.selector.getPreferredSize() + " " + this.selector.getMaximumSize());
430
        this.add(this.selector, c);
83 ilm 431
        this.validate();
432
        this.repaint();
433
 
434
    }
132 ilm 435
 
177 ilm 436
    public boolean isModeSearch() {
437
        return this.selector == this.articleSearchPanel;
438
    }
439
 
132 ilm 440
    public CaisseControler getControler() {
174 ilm 441
        return this.controler;
132 ilm 442
    }
174 ilm 443
 
444
    public void validateTicket(final CaisseFrame caisseFrame) {
445
        final Ticket savedReceipt;
446
        try {
447
            savedReceipt = CaissePanel.this.controler.saveAndClearTicket(caisseFrame.getFiles(), caisseFrame.getConf().getDirectory());
448
        } catch (DifferentDayException ex) {
449
            JOptionPane.showMessageDialog(CaissePanel.this, "Impossible de laisser la caisse ouverte plusieurs jours. Veuillez la clôturer pour pouvoir faire de nouveaux tickets.", "Erreur",
450
                    JOptionPane.ERROR_MESSAGE);
451
            return;
452
        } catch (Throwable ex) {
453
            ExceptionHandler.handle(CaissePanel.this, "Erreur de sauvegarde des informations du ticket", ex);
454
            return;
455
        }
456
        if (savedReceipt != null) {
457
            // Valider
458
            CaissePanel.this.controler.setLCD("Impression de", "votre ticket...", 0);
459
            try {
460
                caisseFrame.getPOSConf().print(savedReceipt, (savedReceipt.isAdditionnalCopyRequested() ? 1 : 0));
461
            } catch (UnsatisfiedLinkError ex) {
462
                JOptionPane.showMessageDialog(CaissePanel.this, "Erreur de configuration de la liaison à l'imprimante");
463
            } catch (Throwable ex) {
464
                ex.printStackTrace();
465
                JOptionPane.showMessageDialog(CaissePanel.this, "Erreur d'impression du ticket");
466
            }
467
 
468
            CaissePanel.this.controler.setLCDDefaultDisplay(2);
469
        } else {
470
            System.err.println("CaissePanel.validateTicket() ticket non sauvé");
471
        }
472
    }
18 ilm 473
}