| /******************************************************************************* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache 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://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| *******************************************************************************/ |
| package org.ofbiz.pos.device.impl; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.math.BigDecimal; |
| import java.net.URL; |
| import java.text.SimpleDateFormat; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Map; |
| |
| import javolution.util.FastMap; |
| import jpos.JposException; |
| import jpos.POSPrinter; |
| import jpos.POSPrinterConst; |
| |
| import org.ofbiz.base.util.Debug; |
| import org.ofbiz.base.util.UtilFormatOut; |
| import org.ofbiz.base.util.UtilURL; |
| import org.ofbiz.base.util.UtilValidate; |
| import org.ofbiz.base.util.string.FlexibleStringExpander; |
| import org.ofbiz.pos.PosTransaction; |
| import org.ofbiz.pos.device.GenericDevice; |
| import org.ofbiz.pos.screen.DialogCallback; |
| import org.ofbiz.pos.screen.PosDialog; |
| import org.ofbiz.pos.screen.PosScreen; |
| |
| public class Receipt extends GenericDevice implements DialogCallback { |
| |
| public static final String module = Receipt.class.getName(); |
| |
| protected static final String ESC = ((char) 0x1b) + ""; |
| protected static final String LF = ((char) 0x0a) + ""; |
| |
| protected static final String ALIGN_CENTER = ESC + "|cA"; |
| protected static final String ALIGN_RIGHT = ESC + "|rA"; |
| |
| protected static final String TEXT_DOUBLE_HEIGHT = ESC + "|4C"; |
| protected static final String TEXT_UNDERLINE = ESC + "|uC"; |
| protected static final String TEXT_BOLD = ESC + "|bC"; |
| |
| protected static final String PAPER_CUT = ESC + "|100fP"; |
| |
| protected SimpleDateFormat[] dateFormat = null; |
| protected String[] storeReceiptTmpl = null; |
| protected String[] custReceiptTmpl = null; |
| protected Map<String, Object> reportTmpl = FastMap.newInstance(); |
| |
| protected static final String[] dateFmtStr = { "EEE, d MMM yyyy HH:mm:ss z", "EEE, d MMM yyyy HH:mm:ss z", "EEE, d MMM yyyy HH:mm:ss z" }; |
| protected static final int[] priceLength = { 7, 7, 7 }; |
| protected static final int[] qtyLength = { 5, 5, 5 }; |
| protected static final int[] descLength = { 25, 25, 0 }; |
| public static final int[] pridLength = { 25, 25, 0 }; |
| protected static final int[] infoLength = { 34, 34, 0 }; |
| protected static final int[] configPaddingLength = { 2, 2, 2 }; |
| protected static final String[] configPadding = { " ", " ", " "}; |
| protected PosTransaction lastTransaction = null; |
| |
| public Receipt(String deviceName, int timeout) { |
| super(deviceName, timeout); |
| this.control = new jpos.POSPrinter(); |
| } |
| |
| @Override |
| protected void initialize() throws JposException { |
| Debug.logInfo("Receipt [" + control.getPhysicalDeviceName() + "] Claimed : " + control.getClaimed(), module); |
| // set map mode to metric - all dimensions specified in 1/100mm units |
| // unit = 1/100 mm - i.e. 1 cm = 10 mm = 10 * 100 units |
| ((jpos.POSPrinter) control).setMapMode(POSPrinterConst.PTR_MM_METRIC); |
| } |
| |
| public void println() { |
| this.println(""); |
| } |
| |
| public void println(String p) { |
| try { |
| ((POSPrinter) control).printNormal(POSPrinterConst.PTR_S_RECEIPT, p + LF); |
| } catch (jpos.JposException e) { |
| Debug.logError(e, module); |
| } |
| } |
| |
| public void printBarcode(String barcode) { |
| // print the orderId bar code (Code 3 of 9) centered (1cm tall, 6cm wide) |
| try { |
| ((POSPrinter) control).printBarCode(POSPrinterConst.PTR_S_RECEIPT, barcode, POSPrinterConst.PTR_BCS_Code39, |
| 10 * 100, 60 * 100, POSPrinterConst.PTR_BC_CENTER, POSPrinterConst.PTR_BC_TEXT_NONE); |
| } catch (JposException e) { |
| Debug.logError(e, module); |
| } |
| } |
| |
| public synchronized void printReport(PosTransaction trans, String resource, Map<String, String> context) { |
| // transaction mode causes all output to be buffered |
| try { |
| ((POSPrinter) control).transactionPrint(POSPrinterConst.PTR_S_RECEIPT, POSPrinterConst.PTR_TP_TRANSACTION); |
| } catch (JposException e) { |
| Debug.logError(e, "Exception while setting jpos.POSPrinter.transactionPrint to transaction mode ", module); |
| } |
| Debug.logInfo("Print Report Requested", module); |
| String[] report = this.readReportTemplate(resource); |
| |
| if (report != null) { |
| for (int i = 0; i < report.length; i++) { |
| if (report[i] != null) { |
| this.printInfo(report[i], context, trans, 2); |
| } |
| } |
| |
| this.println(); |
| this.println(); |
| this.println(PAPER_CUT); |
| } |
| try { |
| ((POSPrinter) control).transactionPrint(POSPrinterConst.PTR_S_RECEIPT, POSPrinterConst.PTR_TP_NORMAL); |
| } catch (JposException e) { |
| Debug.logError(e, "Exception while setting jpos.POSPrinter.transactionPrint to normal mode ", module); |
| } |
| } |
| |
| public void reprintReceipt() { |
| this.reprintReceipt(false); |
| } |
| |
| public void reprintReceipt(boolean reprintStoreCopy) { |
| if (lastTransaction != null) { |
| this.printReceipt(lastTransaction, reprintStoreCopy); |
| } |
| } |
| |
| public void printReceipt(PosTransaction trans, boolean printStoreCopy) { |
| Debug.logInfo("Print Receipt Requested : " + trans.getTransactionId(), module); |
| POSPrinter printer = (POSPrinter) control; |
| this.lastTransaction = trans; |
| |
| try { |
| if (!checkState(printer)) { |
| return; |
| } |
| } catch (JposException e) { |
| Debug.logError(e, module); |
| } |
| |
| if (printStoreCopy) { |
| String[] storeReceipt = this.readStoreTemplate(); |
| int payments = trans.getNumberOfPayments(); |
| for (int i = 0; i < payments; i++) { |
| Map<String, Object> info = trans.getPaymentInfo(i); |
| if (info.containsKey("cardNumber")) { |
| this.printReceipt(trans, storeReceipt, 1, info); |
| } |
| try { |
| Thread.sleep(3000); |
| } catch (Exception e) { |
| Debug.logError(e, module); |
| } |
| } |
| } |
| |
| // print the customer receipt |
| String[] custReceipt = this.readCustomerTemplate(); |
| this.printReceipt(trans, custReceipt, 0, null); |
| } |
| |
| private void printReceipt(PosTransaction trans, String[] template, int type, Map<String, Object> payInfo) { |
| // transaction mode causes all output to be buffered |
| try { |
| ((POSPrinter) control).transactionPrint(POSPrinterConst.PTR_S_RECEIPT, POSPrinterConst.PTR_TP_TRANSACTION); |
| } catch (JposException e) { |
| Debug.logError(e, "Exception while setting jpos.POSPrinter.transactionPrint to transaction mode ", module); |
| } |
| |
| if (template != null) { |
| for (int i = 0; i < template.length; i++) { |
| if (template[i] != null) { |
| if ("[ORDER_BARCODE]".equals(template[i])) { |
| this.printBarcode(trans.getOrderId()); |
| } else if (template[i].startsWith("[DLOOP]")) { |
| this.printDetail(template[i], trans, type); |
| } else if (template[i].startsWith("[PLOOP]")) { |
| this.printPayInfo(template[i], trans, type); |
| } else if (payInfo != null) { |
| this.printPayInfo(template[i], trans, type, payInfo); |
| } else { |
| this.printInfo(template[i], trans, type); |
| } |
| } |
| } |
| |
| this.println(); |
| this.println(); |
| this.println(PAPER_CUT); |
| } |
| try { |
| ((POSPrinter) control).transactionPrint(POSPrinterConst.PTR_S_RECEIPT, POSPrinterConst.PTR_TP_NORMAL); |
| } catch (JposException e) { |
| Debug.logError(e, "Exception while setting jpos.POSPrinter.transactionPrint to normal mode ", module); |
| } |
| } |
| |
| private synchronized String[] readStoreTemplate() { |
| if (this.storeReceiptTmpl == null) { |
| this.storeReceiptTmpl = new String[7]; |
| this.readTemplate(storeReceiptTmpl, "storereceipt.txt", 1); |
| } |
| |
| return this.storeReceiptTmpl; |
| } |
| |
| private synchronized String[] readCustomerTemplate() { |
| if (this.custReceiptTmpl == null) { |
| this.custReceiptTmpl = new String[7]; |
| this.readTemplate(custReceiptTmpl, "custreceipt.txt", 0); |
| } |
| |
| return this.custReceiptTmpl; |
| } |
| |
| private synchronized String[] readReportTemplate(String resource) { |
| String[] template = (String[]) reportTmpl.get(resource); |
| if (template == null) { |
| template = new String[7]; |
| this.readTemplate(template, resource, 2); |
| reportTmpl.put(resource, template); |
| } |
| |
| return template; |
| } |
| |
| private String[] readTemplate(String[] template, String resource, int type) { |
| int currentPart = 0; |
| |
| URL fileUrl = UtilURL.fromResource(resource); |
| StringBuffer buf = new StringBuffer(); |
| |
| try { |
| InputStream in = fileUrl.openStream(); |
| BufferedReader dis = new BufferedReader(new InputStreamReader(in)); |
| |
| String line; |
| while ((line = dis.readLine()) != null) { |
| if (line.trim().startsWith("#")) { |
| String[] code = line.trim().split("\\="); |
| if ("#description.length".equals(code[0])) { |
| try { |
| Receipt.descLength[type] = Integer.parseInt(code[1]); |
| } catch (NumberFormatException e) { |
| Debug.logWarning(e, module); |
| } |
| } else if ("#productId.length".equals(code[0])) { |
| try { |
| Receipt.pridLength[type] = Integer.parseInt(code[1]); |
| } catch (NumberFormatException e) { |
| Debug.logWarning(e, module); |
| } |
| } else if ("#price.length".equals(code[0])) { |
| try { |
| Receipt.priceLength[type] = Integer.parseInt(code[1]); |
| } catch (NumberFormatException e) { |
| Debug.logWarning(e, module); |
| } |
| } else if ("#quantity.length".equals(code[0])) { |
| try { |
| qtyLength[type] = Integer.parseInt(code[1]); |
| } catch (NumberFormatException e) { |
| Debug.logWarning(e, module); |
| } |
| } else if ("#infoString.length".equals(code[0])) { |
| try { |
| Receipt.infoLength[type] = Integer.parseInt(code[1]); |
| } catch (NumberFormatException e) { |
| Debug.logWarning(e, module); |
| } |
| } else if ("#dateFormat".equals(code[0])) { |
| Receipt.dateFmtStr[type] = code[1]; |
| } else if ("#configPadding.length".equals(code[0])) { |
| try { |
| Receipt.configPaddingLength[type] = Integer.parseInt(code[1]); |
| StringBuffer spaces = new StringBuffer(); |
| for (int i=0; i < Receipt.configPaddingLength[type]; i+=1) { |
| spaces.append(" "); |
| } |
| Receipt.configPadding[type] = spaces.toString(); |
| } catch (NumberFormatException e) { |
| Debug.logWarning(e, module); |
| } |
| } |
| } else if (line.trim().startsWith("[BEGIN ITEM LOOP]")) { |
| template[currentPart++] = buf.toString(); |
| buf = new StringBuffer(); |
| buf.append("[DLOOP]"); |
| } else if (line.trim().startsWith("[END ITEM LOOP]")) { |
| template[currentPart++] = buf.toString(); |
| buf = new StringBuffer(); |
| } else if (line.trim().startsWith("[BEGIN PAY LOOP]")) { |
| template[currentPart++] = buf.toString(); |
| buf = new StringBuffer(); |
| buf.append("[PLOOP]"); |
| } else if (line.trim().startsWith("[END PAY LOOP]")) { |
| template[currentPart++] = buf.toString(); |
| buf = new StringBuffer(); |
| } else if (line.trim().startsWith("[ORDER BARCODE]")) { |
| template[currentPart++] = buf.toString(); |
| template[currentPart++] = "[ORDER_BARCODE]"; |
| buf = new StringBuffer(); |
| } else { |
| if (UtilValidate.isEmpty(line)) { |
| line = " "; |
| } |
| buf.append(line + "\n"); |
| } |
| } |
| in.close(); |
| } catch (IOException e) { |
| Debug.logError(e, "Unable to open receipt template", module); |
| } |
| |
| template[currentPart] = buf.toString(); |
| return template; |
| } |
| |
| private synchronized SimpleDateFormat getDateFormat(int type) { |
| if (dateFormat == null) { |
| dateFormat = new SimpleDateFormat[3]; |
| } |
| if (dateFormat[type] == null) { |
| dateFormat[type] = new SimpleDateFormat(Receipt.dateFmtStr[type]); |
| } |
| return dateFormat[type]; |
| } |
| |
| private void printInfo(String template, Map<String, String> context, PosTransaction trans, int type) { |
| Map<String, Object> expandMap = this.makeCodeExpandMap(trans, type); |
| if (context != null) { |
| expandMap.putAll(context); // context overrides |
| } |
| this.printInfo(template, expandMap); |
| } |
| |
| private void printInfo(String template, PosTransaction trans, int type) { |
| this.printInfo(template, null, trans, type); |
| } |
| |
| private void printInfo(String template, Map<String, Object> context) { |
| String toPrint = FlexibleStringExpander.expandString(template, context); |
| if (toPrint.indexOf("\n") > -1) { |
| String[] lines = toPrint.split("\\n"); |
| for (int i = 0; i < lines.length; i++) { |
| this.println(lines[i]); |
| } |
| } else { |
| this.println(toPrint); |
| } |
| } |
| |
| private void printDetail(String loop, PosTransaction trans, int type) { |
| String loopStr = loop.substring(7); |
| int size = trans.size(); |
| for (int i = 0; i < size; i++) { |
| Map<String, Object> expandMap = this.makeCodeExpandMap(trans, type); |
| expandMap.putAll(trans.getItemInfo(i)); |
| // adjust the padding |
| expandMap.put("description", UtilFormatOut.padString((String) expandMap.get("description"), descLength[type], true, ' ')); |
| expandMap.put("productId", UtilFormatOut.padString((String) expandMap.get("productId"), pridLength[type], true, ' ')); |
| expandMap.put("basePrice", UtilFormatOut.padString((String) expandMap.get("basePrice"), priceLength[type], false, ' ')); |
| expandMap.put("subtotal", UtilFormatOut.padString((String) expandMap.get("subtotal"), priceLength[type], false, ' ')); |
| expandMap.put("quantity", UtilFormatOut.padString((String) expandMap.get("quantity"), qtyLength[type], false, ' ')); |
| expandMap.put("adjustments", UtilFormatOut.padString((String) expandMap.get("adjustments"), priceLength[type], false, ' ')); |
| String toPrint = FlexibleStringExpander.expandString(loopStr, expandMap); |
| if (toPrint.indexOf("\n") > -1) { |
| String[] lines = toPrint.split("\\n"); |
| for (int x = 0; x < lines.length; x++) { |
| String trimmed = lines[x].trim(); |
| if (trimmed.length()>1) { // if empty string don't println |
| this.println(lines[x]); |
| } |
| } |
| } else { |
| this.println(toPrint); |
| } |
| |
| if (trans.isAggregatedItem(((String)expandMap.get("productId")).trim())) { |
| List<Map<String, Object>> maps = trans.getItemConfigInfo(i); |
| for (Map<String, Object> map: maps) { |
| expandMap = this.makeCodeExpandMap(trans, type); |
| expandMap.putAll(map); |
| // adjust the padding |
| expandMap.put("configDescription", |
| UtilFormatOut.padString(Receipt.configPadding[type] + (String) expandMap.get("configDescription"), descLength[type], true, ' ')); |
| expandMap.put("productId", UtilFormatOut.padString((String) expandMap.get("productId"), pridLength[type], true, ' ')); |
| expandMap.put("configBasePrice", UtilFormatOut.padString((String) expandMap.get("configBasePrice"), priceLength[type], false, ' ')); |
| expandMap.put("configQuantity", UtilFormatOut.padString((String) expandMap.get("configQuantity"), qtyLength[type], false, ' ')); |
| toPrint = FlexibleStringExpander.expandString(loopStr, expandMap); |
| if (toPrint.indexOf("\n") > -1) { |
| String[] lines = toPrint.split("\\n"); |
| for (int x = 0; x < lines.length; x++) { |
| String trimmed = lines[x].trim(); |
| if (trimmed.length()>1) { |
| this.println(lines[x]); |
| } |
| } |
| } else { |
| this.println(toPrint); |
| } |
| } |
| } |
| } |
| } |
| |
| private void printPayInfo(String loop, PosTransaction trans, int type) { |
| String loopStr = loop.substring(7); |
| int size = trans.getNumberOfPayments(); |
| for (int i = 0; i < size; i++) { |
| Map<String, Object> payInfoMap = trans.getPaymentInfo(i); |
| this.printPayInfo(loopStr, trans, type, payInfoMap); |
| } |
| } |
| |
| private void printPayInfo(String template, PosTransaction trans, int type, Map<String, Object> payInfo) { |
| Map<String, Object> expandMap = this.makeCodeExpandMap(trans, type); |
| expandMap.putAll(payInfo); |
| // adjust the padding |
| expandMap.put("authInfoString", UtilFormatOut.padString((String) expandMap.get("authInfoString"), infoLength[type], false, ' ')); |
| expandMap.put("nameOnCard", UtilFormatOut.padString((String) expandMap.get("nameOnCard"), infoLength[type], false, ' ')); |
| expandMap.put("payInfo", UtilFormatOut.padString((String) expandMap.get("payInfo"), infoLength[type], false, ' ')); |
| expandMap.put("amount", UtilFormatOut.padString((String) expandMap.get("amount"), priceLength[type], false, ' ')); |
| |
| String toPrint = FlexibleStringExpander.expandString(template, expandMap); |
| if (toPrint.indexOf("\n") > -1) { |
| String[] lines = toPrint.split("\\n"); |
| for (int x = 0; x < lines.length; x++) { |
| this.println(lines[x]); |
| } |
| } else { |
| this.println(toPrint); |
| } |
| } |
| |
| private Map<String, Object> makeCodeExpandMap(PosTransaction trans, int type) { |
| Map<String, Object> expandMap = FastMap.newInstance(); |
| SimpleDateFormat fmt = this.getDateFormat(type); |
| String dateString = fmt.format(new Date()); |
| |
| expandMap.put("DOUBLE_HEIGHT", TEXT_DOUBLE_HEIGHT); |
| expandMap.put("CENTER", ALIGN_CENTER); |
| expandMap.put("BOLD", TEXT_BOLD); |
| expandMap.put("UNDERLINE", TEXT_UNDERLINE); |
| expandMap.put("LF", LF); |
| expandMap.put("transactionId", trans.getTransactionId()); |
| expandMap.put("terminalId", trans.getTerminalId()); |
| expandMap.put("userId", trans.getUserId()); |
| expandMap.put("orderId", trans.getOrderId()); |
| expandMap.put("dateStamp", dateString); |
| expandMap.put("drawerNo", Integer.toString(trans.getDrawerNumber())); |
| expandMap.put("taxTotal", UtilFormatOut.padString(UtilFormatOut.formatPrice(trans.getTaxTotal()), priceLength[type], false, ' ')); |
| expandMap.put("grandTotal", UtilFormatOut.padString(UtilFormatOut.formatPrice(trans.getGrandTotal()), priceLength[type], false, ' ')); |
| expandMap.put("totalPayments", UtilFormatOut.padString(UtilFormatOut.formatPrice(trans.getPaymentTotal()), priceLength[type], false, ' ')); |
| expandMap.put("change", UtilFormatOut.padString(UtilFormatOut.formatPrice(trans.getTotalDue().compareTo(BigDecimal.ZERO) < 0 ? trans.getTotalDue().negate() : BigDecimal.ZERO), priceLength[type], false, ' ')); |
| expandMap.put("saleDiscount", UtilFormatOut.padString(UtilFormatOut.formatPrice(trans.GetTotalDiscount().compareTo(BigDecimal.ZERO) != 0 ? trans.GetTotalDiscount() : BigDecimal.ZERO), priceLength[type], false, ' ')); |
| return expandMap; |
| } |
| |
| private boolean checkState(POSPrinter printer) throws JposException { |
| if (printer.getCoverOpen() == true) { |
| // printer is not ready |
| PosScreen.currentScreen.showDialog("main/dialog/error/printernotready", this); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| public void receiveDialogCb(PosDialog dialog) { |
| PosScreen.currentScreen.refresh(); |
| this.reprintReceipt(); |
| } |
| } |