package fr.mch.mdo.restaurant.services.business.managers.products; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableModel; import org.jopendocument.dom.spreadsheet.Sheet; import org.jopendocument.dom.spreadsheet.SpreadSheet; import fr.mch.mdo.logs.ILogger; import fr.mch.mdo.restaurant.beans.IBeanLabelable; import fr.mch.mdo.restaurant.beans.IMdoBean; import fr.mch.mdo.restaurant.beans.IMdoDaoBean; import fr.mch.mdo.restaurant.beans.IMdoDtoBean; import fr.mch.mdo.restaurant.dao.beans.Product; import fr.mch.mdo.restaurant.dao.beans.ProductCategory; import fr.mch.mdo.restaurant.dao.products.IProductsDao; import fr.mch.mdo.restaurant.dao.products.hibernate.DefaultProductsDao; import fr.mch.mdo.restaurant.dto.beans.IAdministrationManagerViewBean; import fr.mch.mdo.restaurant.dto.beans.LocaleDto; import fr.mch.mdo.restaurant.dto.beans.ProductDto; import fr.mch.mdo.restaurant.dto.beans.ProductsManagerViewBean; import fr.mch.mdo.restaurant.dto.beans.RestaurantDto; import fr.mch.mdo.restaurant.dto.beans.RestaurantValueAddedTaxDto; import fr.mch.mdo.restaurant.dto.beans.ValueAddedTaxDto; import fr.mch.mdo.restaurant.exception.MdoBusinessException; import fr.mch.mdo.restaurant.exception.MdoException; import fr.mch.mdo.restaurant.services.business.managers.AbstractAdministrationManagerLabelable; import fr.mch.mdo.restaurant.services.business.managers.ICategoriesManager; import fr.mch.mdo.restaurant.services.business.managers.assembler.DefaultProductsAssembler; import fr.mch.mdo.restaurant.services.business.managers.locales.DefaultLocalesManager; import fr.mch.mdo.restaurant.services.business.managers.locales.ILocalesManager; import fr.mch.mdo.restaurant.services.business.managers.restaurants.DefaultRestaurantsManager; import fr.mch.mdo.restaurant.services.business.managers.restaurants.IRestaurantsManager; import fr.mch.mdo.restaurant.services.logs.LoggerServiceImpl; import fr.mch.mdo.utils.IManagerAssembler; public class DefaultProductsManager extends AbstractAdministrationManagerLabelable implements IProductsManager { /** rateFormat is used formatting BigDecimal rate */ private NumberFormat vatRateFormat; { DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(); dfs.setDecimalSeparator('.'); vatRateFormat = new DecimalFormat("#.00", dfs); } private IRestaurantsManager restaurantsManager; private ICategoriesManager categoriesManager; private ILocalesManager localesManager; private static class LazyHolder { private static IProductsManager instance = new DefaultProductsManager( LoggerServiceImpl.getInstance().getLogger(DefaultProductsManager.class.getName()), DefaultProductsDao.getInstance(), DefaultProductsAssembler.getInstance()); } private DefaultProductsManager(ILogger logger, IProductsDao dao, IManagerAssembler assembler) { super.logger = logger; super.dao = dao; super.assembler = assembler; this.restaurantsManager = DefaultRestaurantsManager.getInstance(); this.categoriesManager = DefaultCategoriesManager.getInstance(); this.localesManager = DefaultLocalesManager.getInstance(); } /** * This constructor is used by ioc */ public DefaultProductsManager() { } public static IProductsManager getInstance() { return LazyHolder.instance; } /** * @return the restaurantsManager */ public IRestaurantsManager getRestaurantsManager() { return restaurantsManager; } /** * @param restaurantsManager the restaurantsManager to set */ public void setRestaurantsManager(IRestaurantsManager restaurantsManager) { this.restaurantsManager = restaurantsManager; } /** * @return the categoriesManager */ public ICategoriesManager getCategoriesManager() { return categoriesManager; } /** * @param categoriesManager the categoriesManager to set */ public void setCategoriesManager(ICategoriesManager categoriesManager) { this.categoriesManager = categoriesManager; } /** * @return the localesManager */ public ILocalesManager getLocalesManager() { return localesManager; } /** * @param localesManager the localesManager to set */ public void setLocalesManager(ILocalesManager localesManager) { this.localesManager = localesManager; } @Override public void processList(IAdministrationManagerViewBean viewBean, LocaleDto locale, boolean... lazy) throws MdoBusinessException { // Do not have to call find all products because we want list of products by restaurants //super.processList(viewBean, userContext, lazy); ProductsManagerViewBean productsManagerViewBean = (ProductsManagerViewBean) viewBean; try { productsManagerViewBean.setLabels(super.getLabels(locale)); productsManagerViewBean.setLanguages(localesManager.getLanguages(locale.getLanguageCode())); productsManagerViewBean.setRestaurants(restaurantsManager.findAll(lazy)); productsManagerViewBean.setCategories(categoriesManager.findAll(lazy)); } catch (Exception e) { logger.error("message.error.administration.business.find.all", e); throw new MdoBusinessException("message.error.administration.business.find.all", e); } } @Override protected String getDefaultLabel(IBeanLabelable mdoBean) { String result = null; if (mdoBean != null) { Product mdoBeanCasted = (Product) mdoBean; result = mdoBeanCasted.getCode(); } return result; } @Override public List<IMdoDtoBean> getList(Long restaurantId) throws MdoBusinessException { List<IMdoDtoBean> result = new ArrayList<IMdoDtoBean>(); try { List<IMdoBean> list = ((IProductsDao) dao).findAllByRestaurant(restaurantId); if (list != null) { result = assembler.marshal(list); } } catch (MdoException e) { logger.error("message.error.administration.business.products.by.restaurant", new Object[] {restaurantId}, e); throw new MdoBusinessException("message.error.administration.business.products.by.restaurant", new Object[] {restaurantId}, e); } return result; } @Override public IMdoDtoBean update(IMdoDtoBean dtoBean) throws MdoBusinessException { Product daoBean = (Product) assembler.unmarshal(dtoBean); try { // Deleting daoBean.getCategories() before inserting new ones Set<ProductCategory> backup = new HashSet<ProductCategory>(daoBean.getCategories()); // Removing daoBean.getCategories().clear(); dao.update(daoBean); // Restoring daoBean.getCategories().addAll(backup); return assembler.marshal((IMdoDaoBean) dao.update(daoBean)); } catch (MdoException e) { logger.error("message.error.administration.business.save", e); throw new MdoBusinessException("message.error.administration.business.save", e); } } @Override public IMdoDtoBean delete(IMdoDtoBean dtoBean) throws MdoBusinessException { // No need to Delete categories before Deleting user because of hibernate mapping all-delete-orphan in collection // Delete dto return super.delete(dtoBean); } @Override public ProductDto findByCode(String restaurantReference, String code) throws MdoException { ProductDto result = null; IMdoDaoBean product = (IMdoDaoBean) ((IProductsDao) dao).find(restaurantReference, code); result = (ProductDto) assembler.marshal(product); return result; } @Override public void importData(String importedFileName, File file) throws MdoBusinessException { try { Pattern pattern = Pattern.compile(IProductsManager.IMPORT_DATA_FILE_NAME_PATTERN); Matcher matcher = pattern.matcher(importedFileName); boolean matchFound = matcher.find(); String restaurantReference = null; String language = null; if (matchFound) { restaurantReference = matcher.group(1); language = matcher.group(2); } else { throw new MdoBusinessException("message.error.administration.business.products.restaurant.not.found", new Object[]{file}); } RestaurantDto restaurant = null; restaurant = (RestaurantDto) restaurantsManager.findByReference(restaurantReference); if (restaurant != null) { LocaleDto locale = null; locale = (LocaleDto) localesManager.findByLanguage(language); if (locale != null) { Set<RestaurantValueAddedTaxDto> vats = restaurant.getVats(); Map<String, ValueAddedTaxDto> vatsRateId = new HashMap<String, ValueAddedTaxDto>(); for (RestaurantValueAddedTaxDto restaurantValueAddedTaxDto : vats) { ValueAddedTaxDto vat = restaurantValueAddedTaxDto.getVat(); // The vat rate from database is never null // If the vat rate is not unique then keep the last one vatsRateId.put(vatRateFormat.format(vat.getRate().doubleValue()), vat); } // Load the file. Sheet sheet = SpreadSheet.createFromFile(file).getSheet(0); logger.debug("Number of import rows " + sheet.getRowCount()); logger.debug("Number of import columns " + sheet.getColumnCount()); int maxRow = sheet.getRowCount(); int maxColumn = sheet.getColumnCount(); for (int i = 1; i < maxRow; i++) { ProductDto product = new ProductDto(); for (int j = 0; j < maxColumn; j++) { Object cellValue = sheet.getCellAt(j, i).getValue(); logger.debug("Cell at column " + j + " and row " + i + " is instance of " + (cellValue!=null?cellValue.getClass():"") + " with value " + cellValue); // Fill product fields like Code, Label, Price ... See ProductRowTable.java ProductRowTable.values()[j].fillValue(product, cellValue); } if (product.getCode() != null && !product.getCode().isEmpty()) { // labels(see ProductRowTable.LABEL.fillValue) has size one Map<Long, String> labels = product.getLabels(); // labels is empty now String label = labels.remove(null); // labels has one size labels.put(locale.getId(), label); // Product from Database ProductDto productFromDatabase = this.findByCode(restaurant.getReference(), product.getCode()); if (productFromDatabase != null) { // Here, set Values that are not in imported file product.setId(productFromDatabase.getId()); product.setCategories(productFromDatabase.getCategories()); if (productFromDatabase.getLabels() != null) { // Merging labels labels = this.mergeLabels(labels, productFromDatabase.getLabels()); } } // Restaurant product.setRestaurant(restaurant); // VAT: see ProductRowTable.VAT.fillValue BigDecimal rate = product.getVat().getRate(); if (rate != null) { String formattedRate = vatRateFormat.format(rate.doubleValue()); ValueAddedTaxDto vat = vatsRateId.get(formattedRate); if (vat != null) { product.setVat(vat); } } if (product.getVat() == null) { throw new MdoBusinessException("message.error.administration.business.products.vat.not.found", new Object[]{file}); } // Save into database super.save(product); } else { // Considerer that is no more row to import in the imported file break; } } } } else { throw new MdoBusinessException("message.error.administration.business.products.restaurant.not.found", new Object[]{file}); } } catch (Exception e) { throw new MdoBusinessException("message.error.administration.business.products.restaurant.not.found", new Object[]{file}); } } /** * Merge 2 maps into 1 by key map. The reference map is the second map but all data from the first map will erase the data from the second map. * @param labels1 first map. * @param labels2 second map. * @return a merged map. */ private Map<Long, String> mergeLabels(Map<Long, String> labels1, Map<Long, String> labels2) { Map<Long, String> result = new HashMap<Long, String>(labels2); result.putAll(labels1); return result; } @Override public String exportData(OutputStream out, String restaurantReference, String[] headers, LocaleDto locale) throws MdoBusinessException { String exportFileName = this.buildExportFileName(restaurantReference, locale); RestaurantDto restaurant; try { restaurant = (RestaurantDto) this.getRestaurantsManager().findByReference(restaurantReference); } catch (MdoException e) { throw new MdoBusinessException(e); } if (restaurant == null) { throw new MdoBusinessException("message.error.administration.business.products.restaurant.not.found", new Object[]{restaurantReference}); } Long localeId = locale.getId(); List<IMdoDtoBean> list = this.getList(restaurant.getId()); int indexRow = 0; // Create the data to export: 0 row, 5 columns by default Object[][] data = new Object[0][5]; if (list != null) { data = new Object[list.size()][5]; for (IMdoDtoBean iMdoDtoBean : list) { ProductDto castedBean = (ProductDto) iMdoDtoBean; data[indexRow++] = new Object[] { castedBean.getCode(), castedBean.getLabels().get(localeId), castedBean.getPrice(), castedBean.getVat().getRate(), castedBean.getColorRGB() }; logger.debug("Row index = " + indexRow + ", Code = " + castedBean.getCode() + ", Label = " + castedBean.getLabels().get(localeId) + ", Price = " + castedBean.getPrice() + ", Vat = " + castedBean.getVat().getRate() + ", Color = " + castedBean.getColorRGB()); } } logger.debug("Rows Size = " + data.length + " - Columns Size = " + (data.length==0?0:data[0].length)); TableModel model = new DefaultTableModel(data, headers); try { // SpreadSheet.createEmpty(model).saveAs(file); SpreadSheet.createEmpty(model).getPackage().save(out); } catch (IOException e) { throw new MdoBusinessException("message.error.administration.business.products.export.data", new Object[]{restaurantReference}); } return exportFileName; } /** * Build the export file name. * @param restaurantReference the restaurant reference. * @param locale the locale for language part. * @return the generated export file name. */ private String buildExportFileName(String restaurantReference, LocaleDto locale) { String language = "xx"; if (locale != null) { language = locale.getLanguageCode(); } String result = IProductsManager.PREFIX_EXPORT_DATA_FILE_NAME + "-" + restaurantReference + "-" + language + "." + IProductsManager.EXTENSION_EXPORT_IMPORT_DATA_FILE_NAME; return result; } @Override public Map<Long, String> lookupProductsCodesByPrefixCode(Long restaurantId, String prefixProductCode) throws Exception { Map<Long, String> result = new HashMap<Long, String>(); IProductsDao daoX = (IProductsDao) dao; if (prefixProductCode != null) { result = daoX.findCodesByPrefixCode(restaurantId, prefixProductCode); } return result; } }