OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 149 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU General Public License Version 3
 * only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
 * copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 */
 
 package org.openconcerto.erp.core.sales.pos.model;

import org.openconcerto.erp.core.sales.pos.model.RegisterFiles.HashMode;
import org.openconcerto.erp.core.sales.pos.model.RegisterLogEntry.ReceiptEntry;
import org.openconcerto.erp.core.sales.pos.model.RegisterLogEntry.RegisterEntry;
import org.openconcerto.erp.core.sales.pos.model.RegisterState.Status;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.CompareUtils;

import java.io.IOException;
import java.nio.file.Path;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;

/**
 * To parse the log file for a register.
 * 
 * @author sylvain
 */
public class RegisterLog {

    // TODO RECEIPT_MODIFICATION
    static public enum EventType {
        REGISTER_OPENING, RECEIPT_CREATION, REGISTER_CLOSURE;
    }

    static private final int VERSION = 2;
    static private final String VERSION_ATTR_NAME = "version";

    static final Element createRootElement() {
        return new Element("registerLog").setAttribute(VERSION_ATTR_NAME, String.valueOf(VERSION));
    }

    private final Path logFile;
    private Document parsed;
    private int version;
    private RegisterEntry opening;

    public RegisterLog(Path logFile) {
        super();
        this.logFile = logFile;
    }

    public final Path getLogFile() {
        return this.logFile;
    }

    public final RegisterLog parse() throws IOException, JDOMException {
        final Document doc = RegisterFiles.parse(this.logFile);
        final List<Element> elems = doc.getRootElement().getChildren();
        if (elems.isEmpty())
            throw new IllegalStateException("Empty log");
        final RegisterLogEntry firstEntry;
        try {
            firstEntry = RegisterLogEntry.parseEntry(elems.get(0));
        } catch (ParseException e) {
            throw new IOException(e);
        }
        if (firstEntry.getType() != EventType.REGISTER_OPENING)
            throw new IllegalStateException("Do not begin with " + EventType.REGISTER_OPENING + " : " + firstEntry);
        this.opening = (RegisterEntry) firstEntry;
        this.parsed = doc;
        this.version = Integer.parseInt(doc.getRootElement().getAttributeValue(VERSION_ATTR_NAME, "1"));
        return this;
    }

    public final Document getDocument() {
        return this.parsed;
    }

    public final int getVersion() {
        return this.version;
    }

    public final RegisterLogEntry getLastEvent() throws ParseException {
        return this.getEvent(-1);
    }

    public final RegisterLogEntry getEvent(final int index) throws ParseException {
        final List<Element> elems = this.parsed.getRootElement().getChildren();
        return RegisterLogEntry.parseEntry(elems.get(CollectionUtils.getValidIndex(elems, index, true)));
    }

    public final List<RegisterLogEntry> getAllEvents() throws ParseException {
        final List<Element> elems = this.parsed.getRootElement().getChildren();
        final List<RegisterLogEntry> res = new ArrayList<>();
        for (final Element elem : elems)
            res.add(RegisterLogEntry.parseEntry(elem));
        return res;
    }

    public final String getLastReceiptHash() throws ParseException {
        final ReceiptEntry lastReceiptCreationEvent = this.getLastReceiptCreationEvent();
        if (lastReceiptCreationEvent != null)
            return lastReceiptCreationEvent.getFileHash();
        else
            return this.getFirstRegisterEvent().getLastReceiptHash();
    }

    public final int getRegisterID() {
        return this.getFirstRegisterEvent().getRegisterID();
    }

    public final RegisterEntry getFirstRegisterEvent() {
        return this.opening;
    }

    public final RegisterEntry getLastRegisterEvent() throws ParseException {
        final RegisterLogEntry lastEvent = this.getLastEvent();
        if (lastEvent.getType() == EventType.REGISTER_CLOSURE)
            return (RegisterEntry) lastEvent;
        else
            return this.opening;
    }

    public final RegisterState getRegisterState() throws ParseException {
        final RegisterEntry lastRegisterEvent = this.getLastRegisterEvent();
        return new RegisterState(lastRegisterEvent.getType() == EventType.REGISTER_OPENING ? Status.OPEN : Status.CLOSED, lastRegisterEvent.getDate());
    }

    public final List<ReceiptEntry> getReceiptEvents() throws ParseException {
        final List<Element> elems = this.parsed.getRootElement().getChildren();
        final int size = elems.size();
        if (size <= 1)
            return Collections.emptyList();
        final List<ReceiptEntry> res = new ArrayList<>();
        for (final Element elem : elems.subList(1, size)) {
            final RegisterLogEntry parsedEntry = RegisterLogEntry.parseEntry(elem);
            if (parsedEntry instanceof ReceiptEntry) {
                res.add((ReceiptEntry) parsedEntry);
            } else if (parsedEntry.getType() == EventType.REGISTER_CLOSURE) {
                if (res.size() != size - 2)
                    throw new IllegalStateException("Closure not at the end : " + elems);
            } else {
                throw new IllegalStateException("Unexpected entry : " + parsedEntry);
            }
        }
        return res;
    }

    /**
     * Parse all receipts logged in this. This also validates that data recorded in this log matches
     * the parsed receipts' files.
     * 
     * @return the list of current receipts in this.
     * @throws ParseException if an entry couldn't be parsed.
     * @throws IOException if the receipts couldn't be parsed.
     */
    public final List<Ticket> parseReceipts() throws ParseException, IOException {
        final List<Ticket> receipts = new ArrayList<>();
        final Path toUse = this.getLogFile().getParent();
        final List<ReceiptEntry> receiptEvents = this.getReceiptEvents();
        String lastHash = this.getFirstRegisterEvent().getLastReceiptHash();
        int index = 1;
        for (final ReceiptEntry receiptEntry : receiptEvents) {
            final Path receiptFile = toUse.resolve(receiptEntry.getCode().getFileName());
            final Ticket receipt;
            try {
                receipt = Ticket.parseFile(receiptFile.toFile(), HashMode.equalTo(receiptEntry.getFileHash()));
            } catch (Exception e) {
                throw new IOException("Couldn't parse file of " + receiptEntry + " : " + receiptFile, e);
            }
            assert receipt != null;
            if (!receipt.getCode().equals(receiptEntry.getCodeString()))
                throw new IllegalStateException("Code mismatch");
            if (!CompareUtils.equals(lastHash, receipt.getPreviousHash()))
                throw new IllegalStateException("Previous hash mismatch for " + receiptEntry.getCodeString());
            lastHash = receiptEntry.getFileHash();
            if (receipt.getNumber() != index)
                throw new IllegalStateException("Invalid index, expected " + index + " got " + receipt.getNumber());
            index++;
            if (receipt.getCreationDate().compareTo(receiptEntry.getDate()) != 0)
                throw new IllegalStateException("Date mismatch for " + receiptEntry.getCodeString() + " logged " + receiptEntry.getDate() + " but receipt " + receipt.getCreationDate());
            receipts.add(receipt);
        }
        return receipts;
    }

    public final ReceiptEntry getLastReceiptCreationEvent() throws ParseException {
        for (int i = -1; i >= -2; i--) {
            final RegisterLogEntry event = this.getEvent(i);
            if (event.getType() == EventType.REGISTER_OPENING) {
                return null;
            } else if (event.getType() == EventType.RECEIPT_CREATION) {
                return (ReceiptEntry) event;
            }
        }
        throw new IllegalStateException("2 " + EventType.REGISTER_CLOSURE);
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + " " + this.getLogFile() + " opened " + this.getFirstRegisterEvent().getDate();
    }
}