OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 151 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
144 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.model;
15
 
16
import org.openconcerto.erp.config.ComptaPropsConfiguration;
17
import org.openconcerto.erp.core.sales.pos.POSConfiguration;
18
import org.openconcerto.erp.core.sales.pos.model.RegisterLog.EventType;
19
import org.openconcerto.erp.core.sales.pos.model.RegisterLogEntry.ReceiptEntry;
20
import org.openconcerto.erp.core.sales.pos.model.RegisterLogEntry.RegisterEntry;
21
import org.openconcerto.erp.core.sales.pos.model.RegisterState.Status;
149 ilm 22
import org.openconcerto.sql.model.Order;
144 ilm 23
import org.openconcerto.sql.model.SQLRow;
24
import org.openconcerto.sql.model.SQLRowListRSH;
25
import org.openconcerto.sql.model.SQLRowValues;
26
import org.openconcerto.sql.model.SQLSelect;
27
import org.openconcerto.sql.model.SQLTable;
149 ilm 28
import org.openconcerto.sql.model.TableRef;
144 ilm 29
import org.openconcerto.sql.model.Where;
30
import org.openconcerto.utils.CompareUtils;
31
import org.openconcerto.utils.Value;
32
 
33
import java.io.IOException;
34
import java.nio.file.Path;
149 ilm 35
import java.sql.SQLException;
144 ilm 36
import java.text.ParseException;
37
import java.util.Date;
38
import java.util.Iterator;
39
import java.util.List;
40
import java.util.Objects;
41
 
42
import org.jdom2.JDOMException;
43
 
44
public class CheckIntegrity {
45
 
156 ilm 46
    public static void main(String[] args) throws JDOMException, IOException {
47
        final POSConfiguration posConf = POSConfiguration.setInstance();
144 ilm 48
        final ComptaPropsConfiguration conf = posConf.createConnexion();
49
        try {
50
            for (final RegisterFiles files : RegisterFiles.scan(posConf.getRootDir())) {
149 ilm 51
                final RegisterDB registerDB = new RegisterDB(conf.getDirectory(), conf.getProductInfo(), files.getPosID());
52
                checkRegisterFiles(registerDB, files);
53
                checkRegisterRow(registerDB, files);
144 ilm 54
            }
55
            System.out.println("\n\nAll done");
149 ilm 56
        } catch (Throwable e) {
144 ilm 57
            e.printStackTrace();
58
        } finally {
59
            posConf.closeConnexion();
60
        }
61
    }
62
 
149 ilm 63
    private static void checkRegisterRow(final RegisterDB registerDB, final RegisterFiles files) throws IOException, JDOMException, ParseException, SQLException {
64
        final RegisterState localRegisterState = files.getLastLog().getRegisterState();
65
        final DBState dbState = registerDB.fetchRegisterState();
66
        final RegisterState remoteRegisterState = dbState.getRegisterState();
67
        if (!localRegisterState.equals(remoteRegisterState))
68
            System.out.println("WARNING FS and DB state not equal (this may be fixed by launching the application) :\n" + localRegisterState + "\n" + remoteRegisterState);
69
 
70
        final SQLTable logT = registerDB.getLogElement().getTable();
71
        final SQLSelect sel = new SQLSelect();
72
        sel.addSelect(logT.getKey());
73
        setWhereAndOrder(sel, registerDB, logT);
74
        final Number lastLogID = (Number) logT.getDBSystemRoot().getDataSource().executeScalar(sel.asString());
75
        final SQLRowValues registerLastEntry = dbState.getLastEntry();
76
        final Number registerLastEntryID = registerLastEntry == null ? null : registerLastEntry.getIDNumber();
77
        if (!Objects.equals(registerLastEntryID, lastLogID))
78
            throw new IllegalStateException("Last log entry referenced by the register " + registerLastEntryID + " isn't the last in the log table " + lastLogID);
79
 
80
        final SQLTable closureT = registerDB.getClosureElement().getTable();
81
        final SQLSelect selClosure = new SQLSelect();
82
        selClosure.addSelect(closureT.getKey());
83
        final TableRef entryRef = selClosure.addJoin("INNER", closureT.getField("ID_ENTREE_JOURNAL")).getJoinedTable();
84
        setWhereAndOrder(selClosure, registerDB, entryRef);
85
        final Number lastClosureID = (Number) logT.getDBSystemRoot().getDataSource().executeScalar(selClosure.asString());
86
        final SQLRowValues registerLastClosure = dbState.getLastClosure();
87
        final Number registerLastClosureID = registerLastClosure == null ? null : registerLastClosure.getIDNumber();
88
        if (!Objects.equals(registerLastClosureID, lastClosureID))
89
            throw new IllegalStateException("Last closure referenced by the register " + registerLastClosureID + " isn't the last in its table " + lastClosureID);
90
    }
91
 
92
    private static void setWhereAndOrder(final SQLSelect sel, final RegisterDB registerDB, final TableRef entryRef) {
93
        sel.setWhere(new Where(entryRef.getField("ID_CAISSE"), "=", registerDB.getPosID()));
94
        sel.addFieldOrder(entryRef.getField("DATE"), Order.desc());
95
        sel.setLimit(1);
96
    }
97
 
98
    private static void checkRegisterFiles(final RegisterDB registerDB, final RegisterFiles files) throws IOException, JDOMException, ParseException {
99
        Value<RegisterLog> lastLog = Value.getNone();
144 ilm 100
        for (final Path logFile : files.findLogFiles()) {
101
            System.out.println("Checking " + logFile);
102
            final RegisterLog log = new RegisterLog(logFile).parse();
103
            try {
149 ilm 104
                checkOneLog(files, lastLog, log, registerDB);
144 ilm 105
                System.out.println("OK for " + logFile);
106
            } catch (Exception e) {
107
                // keep checking other files
151 ilm 108
                System.err.println("Error for " + logFile);
144 ilm 109
                e.printStackTrace();
110
            }
149 ilm 111
            lastLog = Value.getSome(log);
144 ilm 112
        }
113
    }
114
 
149 ilm 115
    private static void checkOneLog(final RegisterFiles files, final Value<RegisterLog> previousLog, final RegisterLog log, final RegisterDB registerDB)
144 ilm 116
            throws IOException, JDOMException, ParseException {
117
        if (log.getFirstRegisterEvent().getRegisterID() != files.getPosID())
118
            throw new IllegalStateException("Opening register ID mismatch");
119
        final SQLTable logT = registerDB.getLogElement().getTable();
120
 
121
        Date cal;
122
        // this checks this log is chained to the previous
149 ilm 123
        if (previousLog.hasValue()) {
124
            final RegisterLogEntry lastEntry = previousLog.getValue().getLastEvent();
125
            if (lastEntry.getType() != EventType.REGISTER_CLOSURE)
144 ilm 126
                throw new IllegalStateException("Previous log isn't closed");
149 ilm 127
            final RegisterEntry previousLogClosure = (RegisterEntry) lastEntry;
144 ilm 128
            if (!Objects.equals(previousLogClosure.getLastReceiptHash(), log.getFirstRegisterEvent().getLastReceiptHash()))
129
                throw new IllegalStateException("Register opening hash mismatch, chain broken");
151 ilm 130
            if (RegisterFiles.isNotChronological(previousLogClosure.getDate(), log.getFirstRegisterEvent().getDate()))
144 ilm 131
                throw new IllegalStateException("Register opening before previous closure");
132
            cal = previousLogClosure.getDate();
133
        } else {
134
            cal = null;
135
        }
149 ilm 136
        final Date expectedPreviousDate;
137
        // getPreviousDate() was added in version 2
138
        if (log.getVersion() < 2 || !previousLog.hasValue()) {
139
            expectedPreviousDate = null;
140
        } else {
141
            expectedPreviousDate = previousLog.getValue().getFirstRegisterEvent().getDate();
142
        }
143
        if (!Objects.equals(expectedPreviousDate, log.getFirstRegisterEvent().getPreviousDate()))
144
            throw new IllegalStateException("Previous opening date of this log (" + log.getFirstRegisterEvent() + ") doesn't match the previous log " + expectedPreviousDate);
144 ilm 145
 
146
        final List<ReceiptEntry> receiptEvents = log.getReceiptEvents();
147
 
148
        // check all event dates are chronological
149
        final List<RegisterLogEntry> allEvents = log.getAllEvents();
150
        for (final RegisterLogEntry e : allEvents) {
151
            final Date newDate = e.getDate();
151 ilm 152
            if (cal != null && RegisterFiles.isNotChronological(cal, newDate))
144 ilm 153
                throw new IllegalStateException("Later event before last one");
154
            cal = newDate;
155
        }
156
 
157
        // check log opening was stored in the DB
158
        checkLogTable(logT, log.getFirstRegisterEvent());
159
        // this checks the hash chain and the coherence of the log and the receipts
160
        final List<Ticket> receipts = log.parseReceipts();
161
        // this checks the closure
162
        if (log.getRegisterState().getStatus() == Status.CLOSED) {
149 ilm 163
            final ReceiptEntry lastReceiptCreationEvent = log.getLastReceiptCreationEvent();
164
            assert receipts.isEmpty() == (lastReceiptCreationEvent == null);
165
            // parseReceipts() has already checked that lastReceiptCreationEvent.getFileHash()
166
            // matches the receipt
167
            // if there's no receipt in this log, opening and closure should match
168
            // the opening has already been checked above to match the previous log
169
            final String lastReceiptHash = receipts.isEmpty() ? log.getFirstRegisterEvent().getLastReceiptHash() : lastReceiptCreationEvent.getFileHash();
144 ilm 170
            final RegisterEntry lastRegisterEvent = log.getLastRegisterEvent();
171
            if (!CompareUtils.equals(lastRegisterEvent.getLastReceiptHash(), lastReceiptHash))
149 ilm 172
                throw new IllegalStateException("Closure receipt hash mismatch, recorded " + lastRegisterEvent.getLastReceiptHash() + " but was " + lastReceiptHash);
144 ilm 173
            if (lastRegisterEvent.getRegisterID() != files.getPosID())
174
                throw new IllegalStateException("Closure register ID mismatch");
175
 
176
            // check log closure was stored in the DB
177
            final Number closureEntryID = checkLogTable(logT, lastRegisterEvent);
178
 
179
            final SQLRow closureRow = getClosureRow(registerDB.getClosureElement().getTable(), lastRegisterEvent, closureEntryID);
180
            // TODO this doesn't check dates
181
            final SQLRowValues expected = RegisterDB.fillRow(new SQLRowValues(closureRow.getTable()), receiptEvents, receipts);
182
            for (final String expectedField : expected.getFields()) {
183
                final Object expectedVal = expected.getObject(expectedField);
184
                final Object dbVal = closureRow.getObject(expectedField);
185
                final boolean equals;
186
                // needed for BigDecimal
187
                if (expectedVal instanceof Comparable) {
188
                    equals = CompareUtils.compare(expectedVal, dbVal) == 0;
189
                } else {
190
                    equals = Objects.equals(expectedVal, dbVal);
191
                }
192
                if (!equals)
193
                    throw new IllegalStateException("Closure row data doesn't match log for " + expectedField + " : " + expectedVal + " " + dbVal);
194
            }
195
        }
196
 
197
        final SQLTable registerT = registerDB.getReceiptElement().getTable();
198
        final SQLSelect selReceipts = new SQLSelect();
199
        selReceipts.addSelectStar(registerT);
200
        selReceipts.setWhere(new Where(registerT.getField("ID_CAISSE"), "=", files.getPosID()));
201
        final Date lowerBound = log.getFirstRegisterEvent().getDate();
202
        final Where dateWhere;
203
        final int expectedRowsCount;
204
        if (log.getRegisterState().getStatus() == Status.CLOSED) {
205
            final Date upperBound = log.getLastEvent().getDate();
206
            dateWhere = new Where(registerT.getField("DATE"), lowerBound, upperBound);
207
            expectedRowsCount = receipts.size();
208
        } else {
209
            dateWhere = new Where(registerT.getField("DATE"), ">=", lowerBound);
210
            // not yet in the DB
211
            expectedRowsCount = 0;
212
        }
213
        selReceipts.andWhere(dateWhere);
214
        selReceipts.addFieldOrder(registerT.getField("DATE"));
215
        final List<SQLRow> receiptRows = SQLRowListRSH.execute(selReceipts);
216
        if (receiptRows.size() != expectedRowsCount)
217
            throw new IllegalStateException("Receipts count in the DB (" + receiptRows.size() + ") doesn't match log (" + expectedRowsCount + ")");
218
        if (expectedRowsCount > 0) {
219
            final Iterator<SQLRow> iter = receiptRows.iterator();
220
            final Iterator<ReceiptEntry> receiptEventsIter = receiptEvents.iterator();
221
            for (final Ticket receipt : receipts) {
222
                final SQLRow row = iter.next();
223
                final ReceiptEntry receiptEvent = receiptEventsIter.next();
224
 
151 ilm 225
                try {
226
                    if (!row.getString("NUMERO").equals(receipt.getCode()))
227
                        throw new IllegalStateException("Code in the DB doesn't match log");
228
                    if (row.getDate("DATE").compareTo(receipt.getCreationCal()) != 0)
229
                        throw new IllegalStateException("Date in the DB doesn't match log");
230
                    if (!row.getString("FILE_HASH").equals(receiptEvent.getFileHash()))
231
                        throw new IllegalStateException("File hash in the DB doesn't match log : " + row.getString("FILE_HASH") + " != " + receiptEvent.getFileHash());
232
                    if (!Objects.equals(row.getString("FILE_HASH_PREVIOUS"), receipt.getPreviousHash()))
233
                        throw new IllegalStateException("Previous file hash in the DB doesn't match log");
156 ilm 234
                    if (row.getLong("TOTAL_TTC") != receipt.getTotalInCents())
235
                        throw new IllegalStateException("TTC in the DB " + row.getLong("TOTAL_TTC") + " doesn't match log " + receipt.getTotalInCents());
236
                    // ATTN the paid amount isn't stored in the DB by
237
                    // POSConfiguration.importReceipts() so we can't check it exactly
238
                    if (row.getLong("TOTAL_TTC") > receipt.getPaidTotal())
239
                        throw new IllegalStateException("Paid amount in the log (" + receipt.getPaidTotal() + ") is less than total in the DB " + row.getLong("TOTAL_TTC"));
151 ilm 240
                } catch (Exception exn) {
241
                    throw new IllegalStateException("Error while checking " + row + " against " + receipt + " in " + log, exn);
242
                }
144 ilm 243
            }
244
            assert !iter.hasNext() && !receiptEventsIter.hasNext();
245
        }
246
    }
247
 
248
    // check log event was stored in the DB
249
    private static final Number checkLogTable(final SQLTable logT, final RegisterEntry entry) {
250
        final SQLSelect sel = new SQLSelect();
251
        sel.addSelect(logT.getKey());
252
        sel.setWhere(new Where(logT.getField("ID_CAISSE"), "=", entry.getRegisterID()));
253
        sel.andWhere(new Where(logT.getField("EVT"), "=", (entry.getType() == EventType.REGISTER_OPENING ? Status.OPEN : Status.CLOSED).name()));
254
        sel.andWhere(new Where(logT.getField("DATE"), "=", entry.getDate()));
255
        final List<?> ids = logT.getDBSystemRoot().getDataSource().executeCol(sel.asString());
256
        if (ids.size() != 1)
257
            throw new IllegalStateException("Not found in the DB : " + entry);
258
        return (Number) ids.get(0);
259
    }
260
 
261
    private static final SQLRow getClosureRow(final SQLTable closureT, final RegisterEntry entry, final Number closureEntryID) {
262
        final SQLSelect sel = new SQLSelect();
263
        sel.addSelectStar(closureT);
264
        sel.setWhere(new Where(closureT.getField("ID_ENTREE_JOURNAL"), "=", closureEntryID));
265
        final List<SQLRow> rows = SQLRowListRSH.execute(sel);
266
        if (rows.size() != 1)
267
            throw new IllegalStateException("Closure row not found for " + entry);
268
        return rows.get(0);
269
    }
270
 
271
}