/* * The contents of this file are subject to the OpenMRS Public License * Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://license.openmrs.org * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and * limitations under the License. * * Copyright (C) OpenHMIS. All Rights Reserved. */ package org.openmrs.module.openhmis.cashier.api; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.api.context.Context; import org.openmrs.module.openhmis.cashier.api.model.Bill; import org.openmrs.module.openhmis.cashier.api.model.SequentialReceiptNumberGeneratorModel; import org.openmrs.module.openhmis.cashier.web.CashierWebConstants; import org.openmrs.patient.impl.LuhnIdentifierValidator; /** * Implements {@link IReceiptNumberGenerator} to generate sequential receipt numbers */ public class SequentialReceiptNumberGenerator implements IReceiptNumberGenerator { private static final Log LOG = LogFactory.getLog(ReceiptNumberGeneratorFactory.class); private ISequentialReceiptNumberGeneratorService service; private SequentialReceiptNumberGeneratorModel model; private LuhnIdentifierValidator checkDigitGenerator; private boolean loaded = false; public SequentialReceiptNumberGenerator() { service = Context.getService(ISequentialReceiptNumberGeneratorService.class); checkDigitGenerator = new LuhnIdentifierValidator(); } @Override public String getName() { return "Sequential Receipt Number Generator"; } @Override public String getDescription() { return "This receipt number generator provides support for grouped sequential receipt numbers."; } @Override public String getConfigurationPage() { return CashierWebConstants.SEQ_RECEIPT_NUMBER_GENERATOR_PAGE; } @Override public boolean isLoaded() { return loaded; } /** * Loads the generator settings from the database. */ @Override public void load() { model = service.getOnly(); loaded = true; } /** * Generates a sequential receipt number for the specified bill. * @param bill The bill to generate a new receipt number for. * @return The receipt number. * @should Create a new receipt number by grouping type * @should Create a new receipt number by sequence type * @should Create a new receipt number using the specified separator * @should Generate and set the correct check digit * @should Update sequence table with the new sequence by group * @should Throw NullPointerException if bill is null. */ @Override public String generateNumber(Bill bill) { if (bill == null) { throw new NullPointerException("The bill must be defined."); } LOG.debug("Generating receipt number for bill " + bill.getUuid() + "..."); String grouping = createGrouping(bill); String sequence = getSequence(grouping); String number = buildReceiptNumber(grouping, sequence); LOG.debug("Generated receipt number '" + number + "' for bill " + bill.getUuid() + "."); return number; } public String generateCheckDigit(String number) { // Remove the separator from the number String numberWithoutSep = number; if (!model.getSeparator().equals("")) { numberWithoutSep = numberWithoutSep.replace(model.getSeparator(), ""); } // Generate the check digit numberWithoutSep = checkDigitGenerator.getValidIdentifier(numberWithoutSep); // Get the check digit from the end of the returned identifier return numberWithoutSep.substring(numberWithoutSep.length() - 1); } private String createGrouping(Bill bill) { String result = ""; switch (model.getGroupingType()) { case CASHIER: result = model.getCashierPrefix() + bill.getCashier().getId(); break; case CASH_POINT: result = model.getCashPointPrefix() + bill.getCashPoint().getId(); break; case CASHIER_AND_CASH_POINT: result = model.getCashierPrefix() + bill.getCashier().getId() + model.getSeparator() + model.getCashPointPrefix() + bill.getCashPoint().getId(); break; default: break; } return result; } private String getSequence(String grouping) { // Do not include the separator when getting the next sequence String groupingWithoutSep = grouping; if (!model.getSeparator().equals("")) { groupingWithoutSep = groupingWithoutSep.replace(model.getSeparator(), ""); } int sequenceNumber = service.reserveNextSequence(groupingWithoutSep); // Any changes to the date/time generation must be also applied to the unit tests that mock the date generation SimpleDateFormat format; Calendar cal = Calendar.getInstance(); String sequence = String.format("%0" + model.getSequencePadding() + "d", sequenceNumber); switch (model.getSequenceType()) { case DATE_COUNTER: format = new SimpleDateFormat("yyMMdd"); sequence = format.format(new Date(cal.getTimeInMillis())) + sequence; break; case DATE_TIME_COUNTER: format = new SimpleDateFormat("yyMMddHHmmss"); sequence = format.format(new Date(cal.getTimeInMillis())) + sequence; break; default: break; } return sequence; } private String buildReceiptNumber(String grouping, String sequence) { String number; if (StringUtils.isEmpty(grouping)) { number = sequence; } else { number = grouping + model.getSeparator() + sequence; } if (model.isIncludeCheckDigit()) { number = number + model.getSeparator() + generateCheckDigit(number); } return number; } /** * The defination of the constants to be used in Sequence receipt generation */ public static enum SequenceType { COUNTER(0), DATE_COUNTER(1), DATE_TIME_COUNTER(2); private int value; private SequenceType(int value) { this.value = value; } } /** * Defination of the constants to be used in grouping. */ public static enum GroupingType { NONE(0), CASHIER(1), CASH_POINT(2), CASHIER_AND_CASH_POINT(3); private int value; private GroupingType(int value) { this.value = value; } } }