OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 156 | 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.core.sales.pos.POSConfiguration;
17
import org.openconcerto.erp.core.sales.pos.element.CaisseJournalSQLElement;
18
import org.openconcerto.erp.core.sales.pos.element.CaisseTicketSQLElement;
19
import org.openconcerto.erp.core.sales.pos.element.ClôtureCaisseSQLElement;
20
import org.openconcerto.erp.core.sales.pos.element.TicketCaisseSQLElement;
21
import org.openconcerto.erp.core.sales.pos.model.RegisterLog.EventType;
22
import org.openconcerto.erp.core.sales.pos.model.RegisterLogEntry.ReceiptEntry;
23
import org.openconcerto.erp.core.sales.pos.model.RegisterLogEntry.RegisterEntry;
24
import org.openconcerto.erp.core.sales.pos.model.RegisterState.Status;
25
import org.openconcerto.sql.element.SQLElementDirectory;
26
import org.openconcerto.sql.model.Order;
27
import org.openconcerto.sql.model.SQLRow;
28
import org.openconcerto.sql.model.SQLRowListRSH;
29
import org.openconcerto.sql.model.SQLRowValues;
30
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
31
import org.openconcerto.sql.model.SQLSelect;
32
import org.openconcerto.sql.model.SQLSelect.LockStrength;
33
import org.openconcerto.sql.model.SQLTable;
34
import org.openconcerto.sql.model.Where;
35
import org.openconcerto.sql.model.graph.Path;
36
import org.openconcerto.sql.model.graph.PathBuilder;
37
import org.openconcerto.sql.utils.SQLUtils;
38
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
149 ilm 39
import org.openconcerto.utils.CollectionUtils;
144 ilm 40
import org.openconcerto.utils.CompareUtils;
41
import org.openconcerto.utils.ProductInfo;
42
import org.openconcerto.utils.TimeUtils;
43
import org.openconcerto.utils.cc.ITransformer;
44
 
174 ilm 45
import java.io.IOException;
144 ilm 46
import java.math.BigDecimal;
47
import java.sql.SQLException;
48
import java.text.ParseException;
49
import java.util.Calendar;
50
import java.util.Date;
51
import java.util.List;
52
import java.util.logging.Level;
53
 
54
public class RegisterDB {
55
 
56
    private final ProductInfo productInfo;
57
    private final CaisseTicketSQLElement registerElem;
58
    private final TicketCaisseSQLElement receiptElem;
59
    private final CaisseJournalSQLElement logElem;
60
    private final ClôtureCaisseSQLElement closureElem;
61
    private final int posID;
62
 
63
    public RegisterDB(final SQLElementDirectory dir, final ProductInfo productInfo, final int caisse) {
64
        super();
65
        this.productInfo = productInfo;
66
        this.registerElem = dir.getElement(CaisseTicketSQLElement.class);
67
        this.receiptElem = dir.getElement(TicketCaisseSQLElement.class);
68
        this.logElem = dir.getElement(CaisseJournalSQLElement.class);
69
        this.closureElem = dir.getElement(ClôtureCaisseSQLElement.class);
70
        this.posID = caisse;
71
    }
72
 
73
    public final SQLTable getRegisterTable() {
74
        return this.registerElem.getTable();
75
    }
76
 
77
    public final TicketCaisseSQLElement getReceiptElement() {
78
        return this.receiptElem;
79
    }
80
 
81
    public final CaisseJournalSQLElement getLogElement() {
82
        return this.logElem;
83
    }
84
 
85
    public final ClôtureCaisseSQLElement getClosureElement() {
86
        return this.closureElem;
87
    }
88
 
89
    public final int getPosID() {
90
        return this.posID;
91
    }
92
 
93
    protected final Path getRegisterToLastClosureEntry() {
94
        return new PathBuilder(getRegisterTable()).addForeignField("ID_DERNIERE_CLOTURE").addForeignField("ID_ENTREE_JOURNAL").build();
95
    }
96
 
97
    public final DBState fetchRegisterState() throws SQLException {
98
        return fetchRegisterState(LockStrength.SHARE);
99
    }
100
 
149 ilm 101
    private final SQLSelect createMostRecentSelect(final SQLTable t, final int limit) {
102
        final SQLSelect res = new SQLSelect();
103
        res.addSelectStar(t);
104
        res.setWhere(new Where(t.getField("ID_CAISSE"), "=", this.getPosID()));
105
        res.addFieldOrder(t.getField("DATE"), Order.desc());
106
        res.setLimit(limit);
107
        res.setLockStrength(LockStrength.SHARE);
108
        return res;
109
    }
110
 
144 ilm 111
    private final DBState fetchRegisterState(final LockStrength lockStrength) throws SQLException {
112
        final SQLRowValues registerVals = new SQLRowValues(getRegisterTable());
113
        registerVals.setAllToNull();
114
        registerVals.assurePath(getRegisterToLastClosureEntry()).setAllToNull();
115
        final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(registerVals);
116
        fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
117
            @Override
118
            public SQLSelect transformChecked(SQLSelect input) {
119
                input.setLockStrength(lockStrength);
120
                input.addLockedTable(registerVals.getTable().getName());
121
                return input;
122
            }
123
        });
124
 
149 ilm 125
        final SQLSelect selLastReceipt = createMostRecentSelect(this.receiptElem.getTable(), 1);
126
        final SQLSelect selLastEntries = createMostRecentSelect(this.getLogElement().getTable(), 3);
144 ilm 127
 
128
        final SQLRowValues registerR = SQLUtils.executeAtomic(getRegisterTable().getDBSystemRoot().getDataSource(), new SQLFactory<SQLRowValues>() {
129
            @Override
130
            public SQLRowValues create() throws SQLException {
131
                final SQLRowValues res = fetcher.fetchOne(getPosID());
132
                if (res == null)
133
                    throw new IllegalStateException("Register not found : " + getPosID());
134
 
149 ilm 135
                final List<SQLRow> lastEntries = SQLRowListRSH.execute(selLastEntries);
136
                for (final SQLRow r : lastEntries) {
137
                    final SQLRowValues vals = r.asRowValues();
138
                    vals.put("ID_CAISSE", res);
139
                }
140
 
144 ilm 141
                final List<SQLRow> receipts = SQLRowListRSH.execute(selLastReceipt);
142
                if (receipts.size() == 1)
143
                    receipts.get(0).asRowValues().put("ID_CAISSE", res);
144
 
145
                res.getGraph().freeze();
146
                return res;
147
            }
148
        });
149
 
150
        return new DBState(this, registerR);
151
    }
152
 
153
    public final DBState open(final String lastLocalHash, final int userID) throws SQLException {
154
        final DBState res = SQLUtils.executeAtomic(getRegisterTable().getDBSystemRoot().getDataSource(), new SQLFactory<DBState>() {
155
            @Override
156
            public DBState create() throws SQLException {
157
                final DBState fetchedState = fetchRegisterState(LockStrength.UPDATE);
158
 
159
                checkStatus(fetchedState, Status.CLOSED);
160
                checkHashes(fetchedState, lastLocalHash);
161
                // set the date here, since the DB is opened before the FS
162
                // TODO support work days other than midnight to midnight by defining a start hour
163
                // then openDay should be the day when the current period started.
164
                // E.g. if a work day starts 25/01 at 15h, even if the register is opened 26/01 at
165
                // 01h then openDay is 25/01
166
                final Date openDay = new Date();
149 ilm 167
 
168
                final SQLRowValues lastOpeningEntry = fetchedState.getLastOpeningEntry();
169
                if (lastOpeningEntry != null && TimeUtils.isSameDay(lastOpeningEntry.getDate("DATE"), openDay))
170
                    throw new IllegalStateException("The state to be created would be in the same day as the previous : " + lastOpeningEntry + " ; " + openDay);
171
 
144 ilm 172
                // verifications OK, proceed to actually open
173
                createUpdateVals(fetchedState, userID, Status.OPEN, openDay).commit();
174
 
175
                return fetchRegisterState();
176
            }
177
        });
178
        POSConfiguration.getLogger().log(Level.INFO, "Finished opening of DB state for register {0}", this.getPosID());
179
        return res;
180
    }
181
 
182
    private final void checkStatus(final DBState fetchedState, final Status expected) {
183
        if (!fetchedState.getRegisterState().getStatus().equals(expected))
184
            throw new IllegalStateException("DB is currently " + fetchedState.getRegisterState());
185
    }
186
 
187
    private final void checkHashes(final DBState fetchedState, final String lastLocalHash) {
188
        final SQLRowValues lastReceipt = fetchedState.getLastReceiptRow();
189
        final String lastDBHash = lastReceipt == null ? null : lastReceipt.getString("FILE_HASH");
190
        if (!CompareUtils.equals(lastLocalHash, lastDBHash))
191
            throw new IllegalStateException("last DB receipt (" + lastDBHash + ") doesn't match last local receipt (" + lastLocalHash + ")");
192
    }
193
 
149 ilm 194
    private final void checkDate(final DBState fetchedState, final Date date) {
144 ilm 195
        final SQLRowValues lastEntry = fetchedState.getLastEntry();
196
        if (lastEntry == null)
197
            return;
198
        final Calendar previousEntryCal = lastEntry.getDate("DATE");
199
        final Date previousEntryDate = previousEntryCal.getTime();
200
        if (previousEntryDate.compareTo(date) >= 0)
201
            throw new IllegalStateException("Previous date is after state to be created : " + previousEntryDate + " >= " + date);
202
    }
203
 
204
    private final SQLRowValues createUpdateVals(final DBState fetchedState, final int userID, final Status newStatus, final Date date) {
149 ilm 205
        checkDate(fetchedState, date);
144 ilm 206
 
207
        final SQLRowValues registerVals = new SQLRowValues(getRegisterTable());
208
        registerVals.setPrimaryKey(fetchedState.getRegisterRow());
149 ilm 209
        final SQLRowValues logVals = new SQLRowValues(getLogElement().getTable());
210
        logVals.put("ID_CAISSE", registerVals);
144 ilm 211
        logVals.put("DATE", date);
212
        logVals.put("ID_USER", userID);
213
        logVals.put("EVT", newStatus.name());
214
        logVals.put("CREATOR", this.productInfo.getFullID());
215
        logVals.put("CREATOR_VERSION", this.productInfo.getVersion());
149 ilm 216
        fetchedState.fillHostValues(logVals);
144 ilm 217
        return registerVals;
218
    }
219
 
220
    // TODO monthly and yearly closures
221
 
174 ilm 222
    public final DBState close(final POSConfiguration posConf, final RegisterLog log) throws SQLException, ParseException, IOException {
144 ilm 223
        final List<ReceiptEntry> receiptEvents = log.getReceiptEvents();
224
        final RegisterEntry closureEntry = log.getLastRegisterEvent();
225
        if (closureEntry.getType() != EventType.REGISTER_CLOSURE)
226
            throw new IllegalArgumentException("Log not closed");
227
        POSConfiguration.checkRegisterID(log.getRegisterID(), this.getPosID());
228
 
229
        final List<Ticket> receipts = log.parseReceipts();
230
        final DBState res = SQLUtils.executeAtomic(getRegisterTable().getDBSystemRoot().getDataSource(), new SQLFactory<DBState>() {
231
            @Override
232
            public DBState create() throws SQLException {
233
                final DBState fetchedState = fetchRegisterState(LockStrength.UPDATE);
234
 
235
                checkStatus(fetchedState, Status.OPEN);
236
                checkHashes(fetchedState, log.getFirstRegisterEvent().getLastReceiptHash());
237
                final Date ourDate = closureEntry.getDate();
238
 
239
                // verifications OK, proceed to import all receipts
156 ilm 240
                posConf.importReceipts(receipts, receiptEvents);
144 ilm 241
 
242
                // actually close
156 ilm 243
                final SQLRowValues registerVals = createUpdateVals(fetchedState, posConf.getUserID(), Status.CLOSED, ourDate);
149 ilm 244
                final SQLRowValues newLogEntry = CollectionUtils.getSole(registerVals.getReferentRows(getLogElement().getTable()));
144 ilm 245
                if (newLogEntry == null)
246
                    throw new IllegalStateException("Missing log entry in " + registerVals);
247
                final SQLRowValues closureVals = registerVals.putRowValues("ID_DERNIERE_CLOTURE");
248
                closureVals.put("ID_ENTREE_JOURNAL", newLogEntry);
249
                closureVals.put("PERIODE", "journalière");
250
                closureVals.put("DEBUT", log.getFirstRegisterEvent().getDate());
251
                closureVals.put("FIN", ourDate);
252
                fillRow(closureVals, receiptEvents, receipts);
253
 
254
                registerVals.commit();
255
 
256
                return fetchRegisterState();
257
            }
258
        });
259
        POSConfiguration.getLogger().log(Level.INFO, "Finished closure of DB state for register {0}", this.getPosID());
260
        return res;
261
    }
262
 
263
    static SQLRowValues fillRow(final SQLRowValues closureVals, final List<ReceiptEntry> receiptEvents, final List<Ticket> receipts) {
264
        BigDecimal totalTTC = BigDecimal.ZERO;
265
        for (final Ticket t : receipts) {
149 ilm 266
            totalTTC = totalTTC.add(BigDecimal.valueOf(t.getPaidTotal()).movePointLeft(2));
144 ilm 267
        }
268
        closureVals.put("TOTAL_TTC", totalTTC);
269
        if (!receiptEvents.isEmpty()) {
270
            closureVals.put("PREMIER_TICKET", receiptEvents.get(0).getCodeString());
271
            closureVals.put("PREMIER_TICKET_HASH", receiptEvents.get(0).getFileHash());
272
            final ReceiptEntry lastReceiptEntry = receiptEvents.get(receiptEvents.size() - 1);
273
            closureVals.put("DERNIER_TICKET", lastReceiptEntry.getCodeString());
274
            closureVals.put("DERNIER_TICKET_HASH", lastReceiptEntry.getFileHash());
275
        }
276
        return closureVals;
277
    }
278
}