// KriolOS POS
// Copyright (c) 2019-2023 KriolOS
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
package com.openbravo.pos.forms;
import com.openbravo.pos.ticket.TicketTaxInfo;
import com.openbravo.pos.ticket.TicketInfo;
import com.openbravo.pos.ticket.TicketLineInfo;
import com.openbravo.pos.ticket.TaxInfo;
import com.openbravo.pos.ticket.ProductInfoExt;
import com.openbravo.pos.ticket.FindTicketsInfo;
import com.openbravo.pos.inventory.UomInfo;
import com.openbravo.pos.inventory.LocationInfo;
import com.openbravo.pos.inventory.ProductsBundleInfo;
import com.openbravo.pos.inventory.TaxCustCategoryInfo;
import com.openbravo.pos.inventory.TaxCategoryInfo;
import com.openbravo.pos.inventory.AttributeSetInfo;
import com.openbravo.basic.BasicException;
import com.openbravo.data.loader.*;
import com.openbravo.data.model.Field;
import com.openbravo.data.model.Row;
import com.openbravo.format.Formats;
import com.openbravo.pos.customers.CustomerTransaction;
import com.openbravo.pos.customers.DataLogicCustomers;
import com.openbravo.pos.forms.AppLocal;
import com.openbravo.pos.forms.BeanFactoryDataSingle;
import com.openbravo.pos.inventory.*;
import com.openbravo.pos.sales.restaurant.FloorsInfo;
import com.openbravo.pos.payment.PaymentInfo;
import com.openbravo.pos.payment.PaymentInfoTicket;
import com.openbravo.pos.pim.DataLogicPIM;
import com.openbravo.pos.sales.ReprintTicketInfo;
import com.openbravo.pos.voucher.DataLogicVouchers;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author adrianromero
* @author jackgerrard
*/
public class DataLogicSales extends BeanFactoryDataSingle {
protected Session sessionDB;
protected Datas[] auxiliarDatas;
protected Datas[] stockdiaryDatas;
protected Datas[] paymenttabledatas;
protected Datas[] stockdatas;
protected Datas[] stockAdjustDatas;
protected Row customersRow;
private static final String PAYMENT_METHOD_DEBT = "debt";
private static final String PAYMENT_METHOD_DEBTPAID = "debtpaid";
private static final String PREPAY = "prepay";
private static final Logger LOGGER = Logger.getLogger("com.openbravo.pos.forms.DataLogicSales");
// SQL constants for inventory panel queries
public static final String SQL_BUNDLE_LIST = "SELECT B.ID, B.PRODUCT, B.PRODUCT_BUNDLE, B.QUANTITY, P.REFERENCE, P.CODE, P.NAME "
+ "FROM products_bundle B, products P "
+ "WHERE B.PRODUCT_BUNDLE = P.ID AND B.PRODUCT = ?";
public static final String SQL_AUXILIAR_LIST = "SELECT COM.ID, COM.PRODUCT, COM.PRODUCT2, P.REFERENCE, P.CODE, P.NAME "
+ "FROM products_com COM, products P "
+ "WHERE COM.PRODUCT2 = P.ID AND COM.PRODUCT = ?";
public DataLogicSales() {
stockdiaryDatas = new Datas[]{
Datas.STRING, Datas.TIMESTAMP, Datas.INT, Datas.STRING,
Datas.STRING, Datas.STRING, Datas.DOUBLE, Datas.DOUBLE,
Datas.STRING, Datas.STRING, Datas.STRING};
paymenttabledatas = new Datas[]{
Datas.STRING, Datas.STRING, Datas.TIMESTAMP,
Datas.STRING, Datas.STRING, Datas.DOUBLE,
Datas.STRING};
stockdatas = new Datas[]{
Datas.STRING, Datas.STRING, Datas.STRING,
Datas.DOUBLE, Datas.DOUBLE, Datas.DOUBLE};
stockAdjustDatas = new Datas[]{
Datas.STRING,
Datas.STRING,
Datas.STRING,
Datas.DOUBLE};
auxiliarDatas = new Datas[]{
Datas.STRING, Datas.STRING, Datas.STRING,
Datas.STRING, Datas.STRING, Datas.STRING};
// creating customers object here for now for future global reuse
// LOYALTY, MEMBERSHIP & etc as will be more system centric than customer
customersRow = new Row(
new Field("ID", Datas.STRING, Formats.STRING),
new Field("SEARCHKEY", Datas.STRING, Formats.STRING),
new Field("TAXID", Datas.STRING, Formats.STRING),
new Field("NAME", Datas.STRING, Formats.STRING),
new Field("TAXCATEGORY", Datas.STRING, Formats.STRING),
new Field("CARD", Datas.STRING, Formats.STRING),
new Field("MAXDEBT", Datas.DOUBLE, Formats.CURRENCY),
new Field("ADDRESS", Datas.STRING, Formats.STRING),
new Field("ADDRESS2", Datas.STRING, Formats.STRING),
new Field("POSTAL", Datas.STRING, Formats.STRING),
new Field("CITY", Datas.STRING, Formats.STRING),
new Field("REGION", Datas.STRING, Formats.STRING),
new Field("COUNTRY", Datas.STRING, Formats.STRING),
new Field("FIRSTNAME", Datas.STRING, Formats.STRING),
new Field("LASTNAME", Datas.STRING, Formats.STRING),
new Field("EMAIL", Datas.STRING, Formats.STRING),
new Field("PHONE", Datas.STRING, Formats.STRING),
new Field("PHONE2", Datas.STRING, Formats.STRING),
new Field("FAX", Datas.STRING, Formats.STRING),
new Field("NOTES", Datas.STRING, Formats.STRING),
new Field("VISIBLE", Datas.BOOLEAN, Formats.BOOLEAN),
new Field("CURDATE", Datas.STRING, Formats.TIMESTAMP),
new Field("CURDEBT", Datas.DOUBLE, Formats.CURRENCY),
new Field("IMAGE", Datas.BYTES, Formats.NULL),
new Field("ISVIP", Datas.BOOLEAN, Formats.BOOLEAN),
new Field("DISCOUNT", Datas.DOUBLE, Formats.CURRENCY),
new Field("MEMODATE", Datas.STRING, Formats.TIMESTAMP));
}
/**
*
* @param s session
*/
@Override
public void init(Session s) {
this.sessionDB = s;
}
// End Import Creates
public final Row getCustomersRow() {
return customersRow;
}
/**
* JG Oct 2016 Called from JPanelTicket
*
* @param pId
* @param location
* @return
* @throws BasicException
*/
public final ProductStock getProductStockState(String pId, String location) throws BasicException {
PreparedSentence preparedSentence = new PreparedSentence(sessionDB,
"SELECT "
+ "products.id, "
+ "locations.id as Location, "
+ "stockcurrent.units AS Current, "
+ "stocklevel.stocksecurity AS Minimum, "
+ "stocklevel.stockmaximum AS Maximum, "
+ "products.pricebuy, "
+ "products.pricesell, "
+ "products.memodate "
+ "FROM locations "
+ "INNER JOIN ((products "
+ "INNER JOIN stockcurrent "
+ "ON products.id = stockcurrent.product) "
+ "LEFT JOIN stocklevel ON products.id = stocklevel.product) "
+ "ON locations.id = stockcurrent.location "
+ "WHERE products.id = ? "
+ "AND locations.id = ?",
SerializerWriteString.INSTANCE,
ProductStock.getSerializerRead());
ProductStock productStock = (ProductStock) preparedSentence.find(pId, location);
return productStock;
}
/**
* JG May 2016 Called from StockManagement
*
* @param pId
* @return
* @throws BasicException
*/
public final List getProductStockList(String pId) throws BasicException {
String SQL_STOCK = """
SELECT
P.ID AS product_id,
L.name AS location_name,
COALESCE(MAX(SC.units), 0) AS current_stock,
MAX(SL.stocksecurity) AS minimum_stock,
MAX(SL.stockmaximum) AS maximum_stock,
ROUND(P.pricebuy, 2) AS price_buy,
-- Standard calculation for price sell + tax
ROUND((P.pricesell * MAX(T.rate)) + P.pricesell, 2) AS price_sell,
P.memodate
FROM
products P
INNER JOIN
taxcategories TC ON P.TAXCAT = TC.ID
INNER JOIN
taxes T ON TC.ID = T.category
LEFT OUTER JOIN
stocklevel SL ON SL.product = P.ID
LEFT OUTER JOIN
stockcurrent SC ON P.ID = SC.product
INNER JOIN
locations L ON SC.location = L.ID
WHERE
P.ID = ?
GROUP BY
P.ID, L.name, P.pricebuy, P.pricesell, P.memodate;
""";
return new PreparedSentence(sessionDB,
SQL_STOCK,
SerializerWriteString.INSTANCE,
ProductStock.getSerializerRead()).list(pId);
}
/**
* JG Sept 2017
*
* @return
* @throws BasicException
*/
public final List getReprintTicketList() throws BasicException {
return (List) new StaticSentence(sessionDB, """
SELECT
T.TICKETID,
T.TICKETTYPE,
R.DATENEW,
P.NAME,
C.NAME,
SUM(PM.TOTAL),
T.STATUS
FROM receipts R
JOIN tickets T ON R.ID = T.ID
LEFT OUTER JOIN payments PM ON R.ID = PM.RECEIPT
LEFT OUTER JOIN customers C ON C.ID = T.CUSTOMER
LEFT OUTER JOIN people P ON T.PERSON = P.ID
GROUP BY
T.ID,
T.TICKETID,
T.TICKETTYPE,
R.DATENEW,
P.NAME,
C.NAME,
T.STATUS
ORDER BY R.DATENEW DESC, T.TICKETID
LIMIT 10
""",
null,
new SerializerReadClass(ReprintTicketInfo.class)).list();
}
/**
*
* @param Id
* @return
* @throws BasicException
*/
public final TicketInfo getReprintTicket(String Id) throws BasicException {
if (Id == null) {
return null;
} else {
Object[] ticketInfoObjArray = (Object[]) new StaticSentence(sessionDB,
"SELECT "
+ "T.TICKETID, "
+ "SUM(PM.TOTAL), "
+ "R.DATENEW, "
+ "P.NAME, "
+ "T.TICKETTYPE, "
+ "C.NAME, "
+ "T.STATUS "
+ "FROM receipts "
+ "R JOIN tickets T ON R.ID = T.ID LEFT OUTER JOIN payments PM "
+ "ON R.ID = PM.RECEIPT LEFT OUTER JOIN customers C "
+ "ON C.ID = T.CUSTOMER LEFT OUTER JOIN people P ON T.PERSON = P.ID "
+ "WHERE T.TICKETID = ?",
SerializerWriteString.INSTANCE,
new SerializerReadBasic(new Datas[]{Datas.SERIALIZABLE})).find(Id);
return ticketInfoObjArray == null ? null : (TicketInfo) ticketInfoObjArray[0];
}
}
// Tickets and Receipt list
public SentenceList getTicketsList() {
return new StaticSentence(sessionDB,
new QBFBuilder("""
SELECT
T.TICKETID,
T.TICKETTYPE,
R.DATENEW,
P.NAME,
C.NAME,
SUM(PM.TOTAL),
T.STATUS
FROM receipts R
JOIN tickets T ON R.ID = T.ID
LEFT OUTER JOIN payments PM ON R.ID = PM.RECEIPT
LEFT OUTER JOIN customers C ON C.ID = T.CUSTOMER
LEFT OUTER JOIN people P ON T.PERSON = P.ID
WHERE ?(QBF_FILTER)
GROUP BY
T.ID,
T.TICKETID,
T.TICKETTYPE,
R.DATENEW,
P.NAME,
C.NAME,
T.STATUS
ORDER BY R.DATENEW DESC, T.TICKETID
""",
new String[]{
"T.TICKETID", "T.TICKETTYPE", "PM.TOTAL", "R.DATENEW",
"R.DATENEW", "P.NAME", "C.NAME"
}),
new SerializerWriteBasic(new Datas[]{
Datas.OBJECT, Datas.INT,
Datas.OBJECT, Datas.INT,
Datas.OBJECT, Datas.DOUBLE,
Datas.OBJECT, Datas.TIMESTAMP,
Datas.OBJECT, Datas.TIMESTAMP,
Datas.OBJECT, Datas.STRING,
Datas.OBJECT, Datas.STRING
}),
new SerializerReadClass(FindTicketsInfo.class));
}
// User list
/**
*
* @return
*/
public final SentenceList getTaxCategoryInfoList() {
return getTaxCategoriesList();
}
/**
* @deprecated since Nov/2025
* @return
*/
public final SentenceList getTaxList() {
return new StaticSentence(sessionDB,
"SELECT "
+ "ID, "
+ "NAME, "
+ "CATEGORY, "
+ "CUSTCATEGORY, "
+ "PARENTID, "
+ "RATE, "
+ "RATECASCADE, "
+ "RATEORDER "
+ "FROM taxes "
+ "ORDER BY NAME",
null,
(DataRead dr) -> new TaxInfo(
dr.getString(1),
dr.getString(2),
dr.getString(3),
dr.getString(4),
dr.getString(5),
dr.getDouble(6),
dr.getBoolean(7),
dr.getInt(8)));
}
public final List getTaxListAll() {
List list = null;
try {
list = this.getTaxList().list();
} catch (BasicException ex) {
LOGGER.log(Level.WARNING, "Cannot get Tax list", ex);
}
return list;
}
/**
*
* @return
*/
public final SentenceList getTaxCustCategoriesList() {
return new StaticSentence<>(sessionDB,
"SELECT "
+ "ID, "
+ "NAME "
+ "FROM taxcustcategories "
+ "ORDER BY NAME",
null,
(DataRead dr) -> new TaxCustCategoryInfo(
dr.getString(1),
dr.getString(2)));
}
/**
* JG Apr 2017 - Revised to return Customer Id - cId param
*
* @param cId
* @return
* @throws BasicException
*/
public final List getCustomersTransactionList(String cId) throws BasicException {
// TODO: TICKETLINE MUST STORE: _tax_value, _line_amount(Qty x price)
// _line_total (Price x Qty x Tax), line_prod_name
// TODO: CALCULATION MUST BE DONE Java using BigDecimal
return new PreparedSentence<>(sessionDB, """
SELECT
tickets.TICKETID,
products.NAME AS PNAME,
SUM(ticketlines.UNITS) AS UNITS,
SUM(ticketlines.UNITS * ticketlines.PRICE) AS AMOUNT,
SUM(ticketlines.UNITS * ticketlines.PRICE * (1.0 + taxes.RATE)) AS TOTAL,
receipts.DATENEW,
customers.ID AS CID
FROM ticketlines ticketlines
INNER JOIN taxes taxes ON ticketlines.TAXID = taxes.ID
INNER JOIN tickets tickets ON tickets.ID = ticketlines.TICKET
INNER JOIN customers customers ON customers.ID = tickets.CUSTOMER
INNER JOIN receipts receipts ON tickets.ID = receipts.ID
LEFT OUTER JOIN products products ON ticketlines.PRODUCT = products.ID
WHERE tickets.CUSTOMER = ?
GROUP BY
customers.ID,
receipts.DATENEW,
tickets.TICKETID,
products.NAME
ORDER BY receipts.DATENEW DESC
""",
SerializerWriteString.INSTANCE,
CustomerTransaction.getSerializerRead()).list(cId);
}
/**
* @deprecated Since Nov/2025
* @return
*/
public final SentenceList getTaxCategoriesList() {
return new StaticSentence<>(sessionDB,
"SELECT "
+ "ID, "
+ "NAME "
+ "FROM taxcategories "
+ "ORDER BY NAME",
null,
(DataRead dr) -> new TaxCategoryInfo(dr.getString(1), dr.getString(2)));
}
/**
*
* @return
*/
public final List getTaxCategoriesListAll() {
List list = null;
try {
list = this.getTaxCategoriesList().list();
} catch (BasicException ex) {
LOGGER.log(Level.WARNING, "Cannot get TaxCategoryInfo list", ex);
}
return list;
}
/**
* @deprecated Since Nov/2025
* @return
*/
public final SentenceList getAttributeSetList() {
return new StaticSentence(sessionDB,
"SELECT "
+ "ID, "
+ "NAME "
+ "FROM attributeset "
+ "ORDER BY NAME",
null,
(DataRead dr) -> new AttributeSetInfo(dr.getString(1), dr.getString(2)));
}
public final List getAttributeSetListAll() {
List list = null;
try {
list = this.getAttributeSetList().list();
} catch (BasicException ex) {
LOGGER.log(Level.WARNING, "Cannot get AttributeSetInfo list", ex);
}
return list;
}
/**
* @deprecated Since Nov/2025
* @return
*/
public final SentenceList getLocationsList() {
return new StaticSentence(sessionDB,
"SELECT "
+ "ID, "
+ "NAME, "
+ "ADDRESS FROM locations "
+ "ORDER BY NAME",
null,
new SerializerReadClass(LocationInfo.class));
}
public final List getLocationsListAll() {
List list = null;
try {
list = this.getLocationsList().list();
} catch (BasicException ex) {
LOGGER.log(Level.WARNING, "Cannot get AttributeSetInfo list", ex);
}
return list;
}
/**
*
* @return
*/
public final SentenceList getFloorsList() {
return new StaticSentence(sessionDB,
"SELECT ID, NAME FROM floors ORDER BY NAME",
null,
new SerializerReadClass(FloorsInfo.class));
}
/**
*
* @return
*/
public final SentenceList getFloorTablesList() {
return new StaticSentence(sessionDB,
"SELECT ID, NAME, SEATS FROM places ORDER BY NAME",
null,
new SerializerReadClass(FloorsInfo.class));
}
/**
*
* @param tickettype
* @param ticketid
* @return
* @throws BasicException
*/
public final TicketInfo loadTicket(final int tickettype, final int ticketid) throws BasicException {
SerializerWrite