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
83 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
185 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
83 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.supplychain.stock.element;
15
 
93 ilm 16
import org.openconcerto.erp.core.sales.product.model.ProductComponent;
17
import org.openconcerto.erp.core.sales.product.model.ProductHelper;
18
import org.openconcerto.erp.core.supplychain.stock.element.StockItem.TypeStockMouvement;
94 ilm 19
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
83 ilm 20
import org.openconcerto.sql.model.DBRoot;
94 ilm 21
import org.openconcerto.sql.model.SQLDataSource;
90 ilm 22
import org.openconcerto.sql.model.SQLRow;
83 ilm 23
import org.openconcerto.sql.model.SQLRowAccessor;
24
import org.openconcerto.sql.model.SQLRowValues;
25
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
26
import org.openconcerto.sql.model.SQLSelect;
27
import org.openconcerto.sql.model.SQLTable;
28
import org.openconcerto.sql.model.Where;
29
import org.openconcerto.sql.utils.SQLUtils;
94 ilm 30
import org.openconcerto.utils.DecimalUtils;
31
import org.openconcerto.utils.ExceptionHandler;
90 ilm 32
import org.openconcerto.utils.ListMap;
83 ilm 33
import org.openconcerto.utils.RTInterruptedException;
94 ilm 34
import org.openconcerto.utils.Tuple3;
83 ilm 35
import org.openconcerto.utils.cc.ITransformer;
36
 
142 ilm 37
import java.awt.GraphicsEnvironment;
38
import java.io.IOException;
39
import java.math.BigDecimal;
40
import java.math.RoundingMode;
41
import java.sql.SQLException;
42
import java.text.SimpleDateFormat;
43
import java.util.ArrayList;
44
import java.util.Date;
45
import java.util.HashMap;
46
import java.util.List;
47
import java.util.Map;
48
 
49
import javax.swing.JOptionPane;
50
import javax.swing.SwingUtilities;
51
 
52
import org.apache.commons.dbutils.ResultSetHandler;
53
 
83 ilm 54
public class StockItemsUpdater {
55
 
56
    private final StockLabel label;
57
    private final List<? extends SQLRowAccessor> items;
93 ilm 58
    private final TypeStockUpdate type;
83 ilm 59
    private final boolean createMouvementStock;
185 ilm 60
    private boolean clearMouvementStock;
83 ilm 61
    private final SQLRowAccessor rowSource;
156 ilm 62
    private boolean resetStockTH = false;
83 ilm 63
 
93 ilm 64
    private boolean headless = false;
83 ilm 65
 
93 ilm 66
    public static enum TypeStockUpdate {
83 ilm 67
 
93 ilm 68
        VIRTUAL_RECEPT(true, TypeStockMouvement.THEORIQUE), REAL_RECEPT(true, TypeStockMouvement.REEL), VIRTUAL_DELIVER(false, TypeStockMouvement.THEORIQUE), REAL_DELIVER(false,
132 ilm 69
                TypeStockMouvement.REEL), REAL_VIRTUAL_RECEPT(true,
70
                        TypeStockMouvement.REEL_THEORIQUE), RETOUR_AVOIR_CLIENT(true, TypeStockMouvement.RETOUR), REAL_VIRTUAL_DELIVER(false, TypeStockMouvement.REEL_THEORIQUE);
83 ilm 71
 
93 ilm 72
        private final boolean entry;
73
        private final TypeStockMouvement type;
74
 
75
        /**
76
         *
77
         * @param entry
78
         */
79
        TypeStockUpdate(boolean entry, TypeStockMouvement type) {
83 ilm 80
            this.entry = entry;
93 ilm 81
            this.type = type;
83 ilm 82
        }
83
 
84
        public boolean isEntry() {
85
            return entry;
86
        }
87
 
93 ilm 88
        public TypeStockMouvement getType() {
89
            return type;
83 ilm 90
        }
91
    };
92
 
93 ilm 93
    public StockItemsUpdater(StockLabel label, SQLRowAccessor rowSource, List<? extends SQLRowAccessor> items, TypeStockUpdate t) {
83 ilm 94
        this(label, rowSource, items, t, true);
95
    }
96
 
93 ilm 97
    public StockItemsUpdater(StockLabel label, SQLRowAccessor rowSource, List<? extends SQLRowAccessor> items, TypeStockUpdate t, boolean createMouvementStock) {
83 ilm 98
        this.label = label;
99
        this.items = items;
100
        this.type = t;
101
        this.createMouvementStock = createMouvementStock;
185 ilm 102
        this.clearMouvementStock = createMouvementStock;
83 ilm 103
        this.rowSource = rowSource;
93 ilm 104
        this.headless = GraphicsEnvironment.isHeadless();
83 ilm 105
    }
106
 
93 ilm 107
    public void setHeadless(boolean headless) {
108
        this.headless = headless;
109
    }
110
 
94 ilm 111
    List<Tuple3<SQLRowAccessor, Integer, BigDecimal>> reliquat = new ArrayList<Tuple3<SQLRowAccessor, Integer, BigDecimal>>();
112
 
113
    public void addReliquat(SQLRowAccessor article, int qte, BigDecimal qteUnit) {
114
        reliquat.add(Tuple3.create(article, qte, qteUnit));
115
    }
116
 
83 ilm 117
    List<String> requests = new ArrayList<String>();
118
 
185 ilm 119
    public void setClearMouvementStock(boolean clearMouvementStock) {
120
        this.clearMouvementStock = clearMouvementStock;
121
    }
122
 
83 ilm 123
    public void update() throws SQLException {
124
        final SQLTable stockTable = this.rowSource.getTable().getTable("STOCK");
125
 
185 ilm 126
        if (this.clearMouvementStock) {
83 ilm 127
            clearExistingMvt(this.rowSource);
128
        }
129
 
130
        // Mise à jour des stocks des articles non composés
131
        List<StockItem> stockItems = fetch();
132
 
90 ilm 133
        final ListMap<SQLRow, SQLRowValues> cmd = new ListMap<SQLRow, SQLRowValues>();
134
 
185 ilm 135
        final List<Integer> updatedIDs = new ArrayList<>();
83 ilm 136
        for (StockItem stockItem : stockItems) {
94 ilm 137
 
83 ilm 138
            if (stockItem.isStockInit()) {
139
                requests.add(stockItem.getUpdateRequest());
185 ilm 140
                updatedIDs.add(stockItem.getStockID());
83 ilm 141
            } else {
142
                SQLRowValues rowVals = new SQLRowValues(stockTable);
143
                rowVals.put("QTE_REEL", stockItem.getRealQty());
144
                rowVals.put("QTE_TH", stockItem.getVirtualQty());
145
                rowVals.put("QTE_LIV_ATTENTE", stockItem.getDeliverQty());
146
                rowVals.put("QTE_RECEPT_ATTENTE", stockItem.getReceiptQty());
156 ilm 147
                rowVals.put("ID_ARTICLE", stockItem.getArticle().getID());
148
                rowVals.put("ID_DEPOT_STOCK", stockItem.stock.getForeignID("ID_DEPOT_STOCK"));
149
                rowVals.commit();
150
                if (stockItem.getArticle().getForeignID("ID_DEPOT_STOCK") == stockItem.stock.getForeignID("ID_DEPOT_STOCK")) {
151
                    SQLRowValues rowValsArt = stockItem.getArticle().createEmptyUpdateRow();
152
                    rowValsArt.put("ID_STOCK", rowVals);
153
                    rowValsArt.commit();
154
                }
83 ilm 155
            }
142 ilm 156
            if (!this.type.isEntry()) {
157
                stockItem.fillCommandeFournisseur(cmd);
158
            }
83 ilm 159
        }
160
 
94 ilm 161
        final List<? extends ResultSetHandler> handlers = new ArrayList<ResultSetHandler>(requests.size());
83 ilm 162
        for (String s : requests) {
163
            handlers.add(null);
164
        }
94 ilm 165
        try {
166
            SQLUtils.executeAtomic(stockTable.getDBSystemRoot().getDataSource(), new ConnectionHandlerNoSetup<Object, IOException>() {
167
                @Override
168
                public Object handle(SQLDataSource ds) throws SQLException, IOException {
169
                    SQLUtils.executeMultiple(stockTable.getDBSystemRoot(), requests, handlers);
185 ilm 170
 
171
                    if (!updatedIDs.isEmpty()) {
172
                        if (updatedIDs.size() <= 60) {
173
                            for (int idUpdated : updatedIDs) {
174
                                stockTable.fireTableModified(idUpdated);
175
                            }
176
                        } else {
177
                            stockTable.fireTableModified();
178
                        }
179
                    }
94 ilm 180
                    return null;
181
                }
182
            });
183
        } catch (IOException e) {
184
            ExceptionHandler.handle("Erreur de la mise à jour des stocks!", e);
185
        }
83 ilm 186
 
187
        final DBRoot root = this.rowSource.getTable().getDBRoot();
188
        if (root.contains("ARTICLE_ELEMENT")) {
93 ilm 189
            // Mise à jour des stocks des nomenclatures
83 ilm 190
            ComposedItemStockUpdater comp = new ComposedItemStockUpdater(root, stockItems);
191
            comp.update();
192
        }
193
 
90 ilm 194
        // FIXME Créer une interface de saisie de commande article en dessous du seuil mini de stock
93 ilm 195
        if (!headless && cmd.size() > 0) {
90 ilm 196
            String msg = "Les articles suivants sont inférieurs au stock minimum : \n";
197
            for (SQLRow row : cmd.keySet()) {
198
                for (SQLRowValues rowVals : cmd.get(row)) {
199
                    msg += rowVals.getString("CODE") + " " + rowVals.getString("NOM") + "\n";
200
                }
201
            }
202
            final String msgFinal = msg;
203
            SwingUtilities.invokeLater(new Runnable() {
204
 
205
                @Override
206
                public void run() {
207
                    JOptionPane.showMessageDialog(null, msgFinal, "Alerte de stock minimum", JOptionPane.WARNING_MESSAGE);
208
                }
209
            });
210
        }
211
 
83 ilm 212
    }
213
 
214
    /**
215
     * Suppression des anciens mouvements
216
     *
217
     * @param rowSource
218
     * @throws SQLException
219
     * @throws RTInterruptedException
220
     */
221
    private void clearExistingMvt(SQLRowAccessor rowSource) throws RTInterruptedException, SQLException {
222
 
223
        List<String> multipleRequests = new ArrayList<String>();
224
 
225
        final SQLTable table = this.rowSource.getTable().getTable("MOUVEMENT_STOCK");
226
        SQLRowValues rowVals = new SQLRowValues(table);
227
        rowVals.put("QTE", null);
228
        rowVals.put("REEL", null);
229
        SQLRowValues rowValsArt = new SQLRowValues(this.rowSource.getTable().getTable("ARTICLE"));
230
        SQLRowValues rowValsStock = new SQLRowValues(this.rowSource.getTable().getTable("STOCK"));
231
        rowValsStock.put("QTE_REEL", null);
232
        rowValsStock.put("QTE_TH", null);
233
        rowValsStock.put("QTE_RECEPT_ATTENTE", null);
234
        rowValsStock.put("QTE_LIV_ATTENTE", null);
235
 
156 ilm 236
        rowVals.put("ID_STOCK", rowValsStock);
83 ilm 237
        rowVals.put("ID_ARTICLE", rowValsArt);
238
 
239
        SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(rowVals);
240
        fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
241
 
242
            @Override
243
            public SQLSelect transformChecked(SQLSelect input) {
244
                Where w = new Where(table.getField("SOURCE"), "=", StockItemsUpdater.this.rowSource.getTable().getName());
245
                w = w.and(new Where(table.getField("IDSOURCE"), "=", StockItemsUpdater.this.rowSource.getID()));
246
                input.setWhere(w);
247
                return input;
248
            }
249
        });
250
 
142 ilm 251
        // On stocke les items pour le calcul total des stocks (sinon le calcul est faux si
252
        // l'article apparait plusieurs fois
253
        // ou si
254
        // on archive un mvt reel et theorique)
255
        Map<Number, StockItem> items = new HashMap<Number, StockItem>();
83 ilm 256
        List<SQLRowValues> result = fetcher.fetch();
257
        for (SQLRowValues sqlRowValues : result) {
142 ilm 258
            final StockItem item;
156 ilm 259
            if (!items.containsKey(sqlRowValues.getForeignIDNumber("ID_STOCK"))) {
260
                item = new StockItem(sqlRowValues.getForeign("ID_ARTICLE"), sqlRowValues.getForeign("ID_STOCK"));
261
                items.put(sqlRowValues.getForeignIDNumber("ID_STOCK"), item);
142 ilm 262
            } else {
156 ilm 263
                item = items.get(sqlRowValues.getForeignIDNumber("ID_STOCK"));
142 ilm 264
            }
93 ilm 265
            final TypeStockMouvement t;
83 ilm 266
            if (sqlRowValues.getBoolean("REEL")) {
93 ilm 267
                t = TypeStockMouvement.REEL;
83 ilm 268
            } else {
93 ilm 269
                t = TypeStockMouvement.THEORIQUE;
83 ilm 270
            }
271
            item.updateQty(sqlRowValues.getFloat("QTE"), t, true);
272
            String req = "UPDATE " + sqlRowValues.getTable().getSQLName().quote() + " SET \"ARCHIVE\"=1 WHERE \"ID\"=" + sqlRowValues.getID();
273
            multipleRequests.add(req);
274
            multipleRequests.add(item.getUpdateRequest());
275
        }
276
 
277
        List<? extends ResultSetHandler> handlers = new ArrayList<ResultSetHandler>(multipleRequests.size());
278
        for (String s : multipleRequests) {
279
            handlers.add(null);
280
        }
281
        SQLUtils.executeMultiple(table.getDBSystemRoot(), multipleRequests, handlers);
282
    }
283
 
284
    /**
93 ilm 285
     * Récupére les stocks associés aux articles non composés (inclus les fils des nomenclatures) et
286
     * les met à jour
83 ilm 287
     *
288
     * @return la liste des stocks à jour
289
     */
290
    private List<StockItem> fetch() {
291
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
156 ilm 292
        Map<Number, StockItem> stockItems = new HashMap<Number, StockItem>();
293
 
93 ilm 294
        String mvtStockTableQuoted = rowSource.getTable().getTable("MOUVEMENT_STOCK").getSQLName().quote();
83 ilm 295
 
174 ilm 296
        ProductHelper helper = new ProductHelper(rowSource.getTable().getDBRoot());
93 ilm 297
        // Liste des éléments à mettre à jour
298
        List<ProductComponent> productComponents = new ArrayList<ProductComponent>();
174 ilm 299
        helper.fillProductComponent(this.items, productComponents, 1, 0, 1);
93 ilm 300
        // for (int i = 0; i < items.size(); i++) {
301
        // SQLRowAccessor r = items.get(i);
302
        //
303
        // // On ne calcul pas les stocks pour les éléments ayant des fils (le mouvement de stock
304
        // // des fils impactera les stocks automatiquement)
305
        // if (r.getTable().contains("NIVEAU")) {
306
        // if (i + 1 < items.size()) {
307
        // SQLRowAccessor rNext = items.get(i + 1);
308
        // if (rNext.getInt("NIVEAU") > r.getInt("NIVEAU")) {
309
        // continue;
310
        // }
311
        // }
312
        // }
313
        // if (!r.isForeignEmpty("ID_ARTICLE")) {
314
        // productComponents.add(ProductComponent.createFrom(r));
315
        // }
316
        // }
83 ilm 317
 
93 ilm 318
        // Liste des articles non composés à mettre à jour (avec les fils des nomenclatures)
319
        List<ProductComponent> boms = helper.getChildWithQtyFrom(productComponents);
83 ilm 320
 
93 ilm 321
        for (ProductComponent productComp : boms) {
322
 
132 ilm 323
            if (productComp.getProduct().getBoolean("GESTION_STOCK") && productComp.getQty().signum() != 0) {
156 ilm 324
                final StockItem stockItem;
325
                if (!stockItems.containsKey(productComp.getStock().getID())) {
326
                    stockItem = new StockItem(productComp.getProduct(), productComp.getStock());
327
                    stockItems.put(productComp.getStock().getID(), stockItem);
328
                } else {
329
                    stockItem = stockItems.get(productComp.getStock().getID());
330
                }
93 ilm 331
                double qteFinal = productComp.getQty().doubleValue();
94 ilm 332
 
333
                // reliquat
334
                for (Tuple3<SQLRowAccessor, Integer, BigDecimal> t : reliquat) {
335
                    if (stockItem.getArticle() != null && stockItem.getArticle().equalsAsRow(t.get0())) {
336
                        double qteFinalReliquat = t.get2().multiply(new BigDecimal(t.get1()), DecimalUtils.HIGH_PRECISION).doubleValue();
337
                        qteFinal -= qteFinalReliquat;
338
                    }
339
                }
93 ilm 340
                if (!this.type.isEntry()) {
341
                    qteFinal = -qteFinal;
342
                }
343
 
344
                stockItem.updateQty(qteFinal, this.type.getType());
156 ilm 345
 
93 ilm 346
                if (this.createMouvementStock) {
347
                    final Date time = this.rowSource.getDate("DATE").getTime();
348
                    BigDecimal prc = productComp.getPRC(time);
132 ilm 349
                    if (this.type.getType() == TypeStockMouvement.REEL || this.type.getType() == TypeStockMouvement.REEL_THEORIQUE || this.type.getType() == TypeStockMouvement.RETOUR) {
156 ilm 350
                        String mvtStockQuery = "INSERT INTO " + mvtStockTableQuoted + " (\"QTE\",\"DATE\",\"ID_ARTICLE\",\"ID_STOCK\",\"SOURCE\",\"IDSOURCE\",\"NOM\",\"REEL\",\"ORDRE\"";
93 ilm 351
 
352
                        if (prc != null) {
353
                            mvtStockQuery += ",\"PRICE\"";
354
                        }
355
 
156 ilm 356
                        mvtStockQuery += ") VALUES(" + qteFinal + ",'" + dateFormat.format(time) + "'," + productComp.getProduct().getID() + "," + productComp.getStock().getID() + ",'"
357
                                + this.rowSource.getTable().getName() + "'," + this.rowSource.getID() + ",'" + this.label.getLabel(this.rowSource, productComp.getProduct())
358
                                + "',true, (SELECT (MAX(\"ORDRE\")+1) FROM " + mvtStockTableQuoted + ")";
93 ilm 359
                        if (prc != null) {
360
                            mvtStockQuery += "," + prc.setScale(6, RoundingMode.HALF_UP).toString();
361
                        }
362
                        mvtStockQuery += ")";
363
                        this.requests.add(mvtStockQuery);
83 ilm 364
                    }
132 ilm 365
                    if (this.type.getType() == TypeStockMouvement.THEORIQUE || this.type.getType() == TypeStockMouvement.REEL_THEORIQUE || this.type.getType() == TypeStockMouvement.RETOUR) {
156 ilm 366
                        String mvtStockQuery = "INSERT INTO " + mvtStockTableQuoted + " (\"QTE\",\"DATE\",\"ID_ARTICLE\",\"ID_STOCK\",\"SOURCE\",\"IDSOURCE\",\"NOM\",\"REEL\",\"ORDRE\"";
93 ilm 367
                        if (prc != null) {
368
                            mvtStockQuery += ",\"PRICE\"";
369
                        }
370
 
156 ilm 371
                        mvtStockQuery += ") VALUES(" + qteFinal + ",'" + dateFormat.format(time) + "'," + productComp.getProduct().getID() + "," + productComp.getStock().getID() + ",'"
372
                                + this.rowSource.getTable().getName() + "'," + this.rowSource.getID() + ",'" + this.label.getLabel(this.rowSource, productComp.getProduct())
373
                                + "',false, (SELECT (MAX(\"ORDRE\")+1) FROM " + mvtStockTableQuoted + ")";
93 ilm 374
                        if (prc != null) {
375
                            mvtStockQuery += "," + prc.setScale(6, RoundingMode.HALF_UP).toString();
376
                        }
377
                        mvtStockQuery += ")";
83 ilm 378
                        this.requests.add(mvtStockQuery);
379
                    }
380
                }
381
            }
382
        }
93 ilm 383
 
156 ilm 384
        return new ArrayList<StockItem>(stockItems.values());
83 ilm 385
    }
156 ilm 386
 
387
    public void setResetStockTH(boolean resetStockTH) {
388
        this.resetStockTH = resetStockTH;
389
    }
83 ilm 390
}