package de.metas.ui.web.handlingunits; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; import de.metas.bpartner.BPartnerId; import de.metas.handlingunits.HuId; import de.metas.handlingunits.IHandlingUnitsDAO; import de.metas.handlingunits.model.I_M_HU; import de.metas.handlingunits.model.X_M_HU; import de.metas.handlingunits.report.HUToReport; import de.metas.handlingunits.storage.IHUProductStorage; import de.metas.order.OrderLineId; import de.metas.product.ProductId; import de.metas.ui.web.exceptions.EntityNotFoundException; import de.metas.ui.web.handlingunits.report.HUEditorRowAsHUToReport; import de.metas.ui.web.view.IViewRow; import de.metas.ui.web.view.ViewRowFieldNameAndJsonValues; import de.metas.ui.web.view.ViewRowFieldNameAndJsonValuesHolder; import de.metas.ui.web.view.descriptor.annotation.ViewColumn; import de.metas.ui.web.view.descriptor.annotation.ViewColumn.ViewColumnLayout; import de.metas.ui.web.view.descriptor.annotation.ViewColumn.ViewColumnLayout.Displayed; import de.metas.ui.web.view.json.JSONViewDataType; import de.metas.ui.web.window.datatypes.DocumentId; import de.metas.ui.web.window.datatypes.DocumentPath; import de.metas.ui.web.window.datatypes.LookupValue; import de.metas.ui.web.window.datatypes.LookupValue.IntegerLookupValue; import de.metas.ui.web.window.datatypes.MediaType; import de.metas.ui.web.window.datatypes.WindowId; import de.metas.ui.web.window.datatypes.json.JSONLookupValue; import de.metas.ui.web.window.descriptor.DocumentFieldWidgetType; import de.metas.ui.web.window.descriptor.WidgetSize; import de.metas.util.Check; import de.metas.util.Services; import de.metas.util.StringUtils; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; import org.adempiere.ad.trx.api.ITrx; import org.adempiere.exceptions.AdempiereException; import org.adempiere.model.InterfaceWrapperHelper; import org.compiere.model.I_C_UOM; import org.compiere.model.I_M_Product; import org.compiere.util.Env; import javax.annotation.Nullable; import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Function; /* * #%L * metasfresh-webui-api * %% * Copyright (C) 2017 metas GmbH * %% * 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 2 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 * <http://www.gnu.org/licenses/gpl-2.0.html>. * #L% */ /** * HU Editor's row * * @author metas-dev <[email protected]> */ @EqualsAndHashCode public final class HUEditorRow implements IViewRow { private static final String SYSCFG_PREFIX = "de.metas.ui.web.handlingunits.field"; public static Builder builder(final WindowId windowId) { return new Builder(windowId); } public static HUEditorRow cast(final IViewRow viewRow) { return (HUEditorRow)viewRow; } private final DocumentPath documentPath; private final HUEditorRowId rowId; private final HUEditorRowType type; private final boolean topLevel; private final boolean processed; private final BPartnerId bpartnerId; public static final String FIELDNAME_M_HU_ID = I_M_HU.COLUMNNAME_M_HU_ID; @ViewColumn(fieldName = FIELDNAME_M_HU_ID, widgetType = DocumentFieldWidgetType.Integer) private final HuId huId; public static final String FIELDNAME_HUCode = I_M_HU.COLUMNNAME_Value; @ViewColumn(fieldName = FIELDNAME_HUCode, captionKey = "HUCode",// widgetSize = WidgetSize.Small,// widgetType = DocumentFieldWidgetType.Text, // layouts = { @ViewColumnLayout(when = JSONViewDataType.grid, seqNo = 10), @ViewColumnLayout(when = JSONViewDataType.includedView, seqNo = 10) }) private final String code; public static final String FIELDNAME_Locator = I_M_HU.COLUMNNAME_M_Locator_ID; @ViewColumn(fieldName = FIELDNAME_Locator, // captionKey = FIELDNAME_Locator, // widgetType = DocumentFieldWidgetType.Text, // layouts = { @ViewColumnLayout(when = JSONViewDataType.grid, seqNo = 15, // displayed = Displayed.SYSCONFIG, displayedSysConfigPrefix = SYSCFG_PREFIX, defaultDisplaySysConfig = false) }) private final JSONLookupValue locator; public static final String FIELDNAME_Product = I_M_HU.COLUMNNAME_M_Product_ID; @ViewColumn(fieldName = FIELDNAME_Product, widgetType = DocumentFieldWidgetType.Lookup, sorting = false, layouts = { @ViewColumnLayout(when = JSONViewDataType.grid, seqNo = 20), @ViewColumnLayout(when = JSONViewDataType.includedView, seqNo = 20) }) private final JSONLookupValue product; public static final String FIELDNAME_IsOwnPalette = I_M_HU.COLUMNNAME_HUPlanningReceiptOwnerPM; @ViewColumn(fieldName = FIELDNAME_IsOwnPalette, widgetType = DocumentFieldWidgetType.YesNo, sorting = false, layouts = { @ViewColumnLayout(when = JSONViewDataType.grid, seqNo = 25) }) private final Boolean isOwnPalette; public static final String FIELDNAME_HU_UnitType = "HU_UnitType"; @ViewColumn(fieldName = FIELDNAME_HU_UnitType, // widgetType = DocumentFieldWidgetType.Text, // sorting = false, // restrictToMediaTypes = { MediaType.SCREEN }, // layouts = { @ViewColumnLayout(when = JSONViewDataType.grid, seqNo = 30) }) private final JSONLookupValue huUnitType; public static final String FIELDNAME_PackingInfo = I_M_HU.COLUMNNAME_M_HU_PI_Item_Product_ID; @ViewColumn(fieldName = FIELDNAME_PackingInfo, // captionKey = FIELDNAME_PackingInfo, // widgetType = DocumentFieldWidgetType.Text, // layouts = { @ViewColumnLayout(when = JSONViewDataType.grid, seqNo = 40, // displayed = Displayed.SYSCONFIG, displayedSysConfigPrefix = SYSCFG_PREFIX) }) private final String packingInfo; public static final String FIELDNAME_QtyCU = "QtyCU"; @ViewColumn(fieldName = FIELDNAME_QtyCU, // widgetType = DocumentFieldWidgetType.Quantity,// widgetSize = WidgetSize.Small, sorting = false, layouts = { @ViewColumnLayout(when = JSONViewDataType.grid, seqNo = 50), @ViewColumnLayout(when = JSONViewDataType.includedView, seqNo = 50) }) private final BigDecimal qtyCU; public static final String FIELDNAME_UOM = I_M_Product.COLUMNNAME_C_UOM_ID; @ViewColumn(fieldName = FIELDNAME_UOM, // captionKey = FIELDNAME_UOM, // widgetType = DocumentFieldWidgetType.Text, // layouts = { @ViewColumnLayout(when = JSONViewDataType.grid, seqNo = 60, // displayed = Displayed.SYSCONFIG, displayedSysConfigPrefix = SYSCFG_PREFIX) }) private final JSONLookupValue uom; public static final String FIELDNAME_HUStatus = I_M_HU.COLUMNNAME_HUStatus; @ViewColumn(fieldName = FIELDNAME_HUStatus,// widgetType = DocumentFieldWidgetType.Lookup, // widgetSize = WidgetSize.Small,// sorting = false, layouts = { @ViewColumnLayout(when = JSONViewDataType.grid, seqNo = 70), }) private final JSONLookupValue huStatusDisplay; public static final String FIELDNAME_IsReserved = I_M_HU.COLUMNNAME_IsReserved; private final boolean huReserved; private final String huStatus; public static final String FIELDNAME_BestBeforeDate = "BestBeforeDate"; @ViewColumn(fieldName = FIELDNAME_BestBeforeDate, widgetType = DocumentFieldWidgetType.LocalDate, layouts = { @ViewColumnLayout(when = JSONViewDataType.grid, seqNo = 80, displayed = Displayed.FALSE), @ViewColumnLayout(when = JSONViewDataType.includedView, seqNo = 80, displayed = Displayed.FALSE) }) private final LocalDate bestBeforeDate; private final Optional<HUEditorRowAttributesSupplier> attributesSupplier; private final List<HUEditorRow> includedRows; @Getter private final ImmutableMultimap<OrderLineId, HUEditorRow> includedOrderLineReservations; private transient String _summary; // lazy private final ViewRowFieldNameAndJsonValuesHolder<HUEditorRow> values = ViewRowFieldNameAndJsonValuesHolder.newInstance(HUEditorRow.class); private HUEditorRow(@NonNull final Builder builder) { documentPath = builder.getDocumentPath(); rowId = builder.getRowId(); type = builder.getType(); topLevel = builder.isTopLevel(); processed = builder.isProcessed(); bpartnerId = builder.getBPartnerId(); huId = rowId.getHuId(); code = builder.code; huUnitType = builder.huUnitType; huStatus = builder.huStatus; huReserved = builder.huReserved; huStatusDisplay = builder.huStatusDisplay; packingInfo = builder.packingInfo; product = builder.product; isOwnPalette = builder.isOwnPalette; uom = builder.uom; qtyCU = builder.qtyCU; bestBeforeDate = builder.getBestBeforeDate(); locator = builder.getLocator(); includedRows = builder.buildIncludedRows(); includedOrderLineReservations = builder.prepareIncludedOrderLineReservations(this); final HUEditorRowAttributesProvider attributesProvider = builder.getAttributesProviderOrNull(); if (attributesProvider != null) { attributesSupplier = Optional.of(HUEditorRowAttributesSupplier.builder() .viewRowId(rowId.toDocumentId()) .huId(huId) .provider(attributesProvider) .build()); } else { attributesSupplier = Optional.empty(); } } @Override public String toString() { return MoreObjects.toStringHelper(this) .add("rowId", rowId) .add("summary", getSummary()) .toString(); } @Override public DocumentPath getDocumentPath() { return documentPath; } public HUEditorRowId getHURowId() { return rowId; } @Override public DocumentId getId() { return getHURowId().toDocumentId(); } /** * @return {@link HUEditorRowType}; never returns null. */ @Override public HUEditorRowType getType() { return type; } @Override public boolean isProcessed() { return processed; } public BPartnerId getBPartnerId() { return bpartnerId; } @Override public ImmutableSet<String> getFieldNames() { return values.getFieldNames(); } @Override public ViewRowFieldNameAndJsonValues getFieldNameAndJsonValues() { return values.get(this); } @Override public boolean hasAttributes() { return attributesSupplier.isPresent(); } @Override public HUEditorRowAttributes getAttributes() throws EntityNotFoundException { if (!attributesSupplier.isPresent()) { throw new EntityNotFoundException("row does not support attributes"); } final HUEditorRowAttributes attributes = attributesSupplier.get().get(); if (attributes == null) { throw new EntityNotFoundException("row does not support attributes"); } return attributes; } public Optional<HUEditorRowAttributesSupplier> getAttributesSupplier() { return attributesSupplier; } @Override public List<HUEditorRow> getIncludedRows() { return includedRows; } public Optional<HUEditorRow> getIncludedRowById(final DocumentId rowId) { return streamRecursive() .filter(row -> rowId.equals(row.getId())) .map(HUEditorRow::cast) .findFirst(); } public boolean hasDirectChild(final DocumentId childId) { return getIncludedRows() .stream() .filter(row -> childId.equals(row.getId())) .findAny() .isPresent(); } public HuId getHuId() { return huId; } /** * @return the wrapped HU or {@code null} if there is none. */ public I_M_HU getM_HU() { final HuId huId = getHuId(); if (huId == null) { return null; } return Services.get(IHandlingUnitsDAO.class).getById(huId); } public HUToReport getAsHUToReport() { final HUToReport huToReport = getAsHUToReportOrNull(); if (huToReport == null) { throw new AdempiereException("Cannot convert " + this + " to " + HUToReport.class); } return huToReport; } public HUToReport getAsHUToReportOrNull() { // allow reports for all types ; see task https://github.com/metasfresh/metasfresh/issues/5540 return HUEditorRowAsHUToReport.of(this); } public boolean isHUPlanningReceiptOwnerPM() { // TODO: cache it or better it shall be provided when the row is created final I_M_HU hu = getM_HU(); if (hu == null) { return false; } return hu.isHUPlanningReceiptOwnerPM(); } public String getValue() { return code; } public String getHUStatus() { return huStatus; } public JSONLookupValue getHUStatusDisplay() { return huStatusDisplay; } public boolean isHUStatusPlanning() { return X_M_HU.HUSTATUS_Planning.equals(huStatus); } public boolean isHUStatusActive() { return X_M_HU.HUSTATUS_Active.equals(huStatus); } public boolean isHUStatusDestroyed() { return X_M_HU.HUSTATUS_Destroyed.equals(huStatus); } public boolean isPureHU() { return getType().isPureHU(); } public boolean isCU() { return getType().isCU(); } public boolean isTU() { return getType() == HUEditorRowType.TU; } public boolean isLU() { return getType() == HUEditorRowType.LU; } public boolean hasIncludedTUs() { return getIncludedRows().stream().anyMatch(HUEditorRow::isTU); } public boolean isTopLevel() { return topLevel; } public String getSummary() { if (_summary == null) { _summary = buildSummary(); } return _summary; } private String buildSummary() { final StringBuilder summary = new StringBuilder(); final String value = getValue(); if (!Check.isEmpty(value, true)) { summary.append(value); } final String packingInfo = getPackingInfo(); if (!Check.isEmpty(packingInfo, true)) { if (summary.length() > 0) { summary.append(" "); } summary.append(packingInfo); } return summary.toString(); } public JSONLookupValue getProduct() { return product; } public ProductId getProductId() { final JSONLookupValue productLV = getProduct(); return productLV != null ? ProductId.ofRepoId(productLV.getKeyAsInt()) : null; } public String getM_Product_DisplayName() { final JSONLookupValue productLV = getProduct(); return productLV == null ? null : productLV.getCaption(); } public String getPackingInfo() { return packingInfo; } public JSONLookupValue getUOM() { return uom; } /** * @return the ID of the wrapped UOM or {@code -1} if there is none. */ public int getC_UOM_ID() { final JSONLookupValue uomLV = getUOM(); return uomLV == null ? -1 : uomLV.getKeyAsInt(); } /** * @return the wrapped UOM or {@code null} if there is none. */ public I_C_UOM getC_UOM() { final int uomId = getC_UOM_ID(); if (uomId <= 0) { return null; } return InterfaceWrapperHelper.create(Env.getCtx(), uomId, I_C_UOM.class, ITrx.TRXNAME_None); } /** * The CU qty of this line. Generally {@code null}, unless this line represents exactly one {@link IHUProductStorage}. */ public BigDecimal getQtyCU() { return qtyCU; } public LookupValue toLookupValue() { return IntegerLookupValue.of(HuId.toRepoId(getHuId()), getSummary()); } /** * @param stringFilter * @param adLanguage AD_Language (used to get the right row's string representation) * @return true if the row is matching the string filter */ public boolean matchesStringFilter(final String stringFilter) { if (Check.isEmpty(stringFilter, true)) { return true; } final String rowDisplayName = getSummary(); final Function<String, String> normalizer = s -> StringUtils.stripDiacritics(s.trim()).toLowerCase(); final String rowDisplayNameNorm = normalizer.apply(rowDisplayName); final String stringFilterNorm = normalizer.apply(stringFilter); return rowDisplayNameNorm.contains(stringFilterNorm); } // // // // // public static final class Builder { private final WindowId windowId; private HUEditorRowId _rowId; private Boolean topLevel; private HUEditorRowType type; private Boolean processed; private String code; private JSONLookupValue huUnitType; private String huStatus; private boolean huReserved; private JSONLookupValue huStatusDisplay; private String packingInfo; private JSONLookupValue product; private Boolean isOwnPalette; private JSONLookupValue uom; private BigDecimal qtyCU; private LocalDate bestBeforeDate; private JSONLookupValue locator; private BPartnerId bpartnerId; private List<HUEditorRow> includedRows = null; private OrderLineId orderLineReservation = null; private HUEditorRowAttributesProvider attributesProvider; private Builder(@NonNull final WindowId windowId) { this.windowId = windowId; } public HUEditorRow build() { return new HUEditorRow(this); } private DocumentPath getDocumentPath() { final HUEditorRowId rowId = getRowId(); return DocumentPath.rootDocumentPath(windowId, rowId.getHuId()); } public Builder setRowId(final HUEditorRowId rowId) { _rowId = rowId; return this; } /** * @return row ID */ private HUEditorRowId getRowId() { return Check.assumeNotNull(_rowId, "Parameter rowId is not null"); } private HUEditorRowType getType() { return Check.assumeNotNull(type, "Parameter type is not null"); } public Builder setType(final HUEditorRowType type) { this.type = type; return this; } public Builder setTopLevel(final boolean topLevel) { this.topLevel = topLevel; return this; } private boolean isTopLevel() { return Check.assumeNotNull(topLevel, "Parameter topLevel is not null"); } public Builder setProcessed(final boolean processed) { this.processed = processed; return this; } private boolean isProcessed() { if (processed == null) { // NOTE: don't take the "Processed" field if any, because in frontend we will end up with a lot of grayed out completed sales orders, for example. // return DisplayType.toBoolean(values.getOrDefault("Processed", false)); return false; } else { return processed.booleanValue(); } } public Builder setCode(final String code) { this.code = code; return this; } public Builder setHUUnitType(final JSONLookupValue huUnitType) { this.huUnitType = huUnitType; return this; } public Builder setHUStatus(final String huStatus) { this.huStatus = Check.assumeNotEmpty(huStatus, "Parameter huStatus may not be empty"); return this; } public Builder setHUStatusDisplay(final JSONLookupValue huStatusDisplay) { this.huStatusDisplay = Check.assumeNotNull(huStatusDisplay, "Parameter huStatusDisplay may not be null"); return this; } public Builder setPackingInfo(final String packingInfo) { this.packingInfo = packingInfo; return this; } public Builder setProduct(final JSONLookupValue product) { this.product = product; return this; } public Builder setIsOwnPalette(final Boolean isOwnPalette) { this.isOwnPalette = isOwnPalette; return this; } public Builder setUOM(final JSONLookupValue uom) { this.uom = uom; return this; } public Builder setQtyCU(final BigDecimal qtyCU) { this.qtyCU = qtyCU; return this; } public Builder setBestBeforeDate(final LocalDate bestBeforeDate) { this.bestBeforeDate = bestBeforeDate; return this; } private LocalDate getBestBeforeDate() { return bestBeforeDate; } public Builder setLocator(final JSONLookupValue locator) { this.locator = locator; return this; } private JSONLookupValue getLocator() { return locator; } public Builder setBPartnerId(final BPartnerId bpartnerId) { this.bpartnerId = bpartnerId; return this; } private BPartnerId getBPartnerId() { return bpartnerId; } private HUEditorRowAttributesProvider getAttributesProviderOrNull() { return attributesProvider; } public Builder setAttributesProvider(@Nullable final HUEditorRowAttributesProvider attributesProvider) { this.attributesProvider = attributesProvider; return this; } public Builder addIncludedRow(final HUEditorRow includedRow) { if (includedRows == null) { includedRows = new ArrayList<>(); } includedRows.add(includedRow); return this; } private List<HUEditorRow> buildIncludedRows() { if (includedRows == null || includedRows.isEmpty()) { return ImmutableList.of(); } return ImmutableList.copyOf(includedRows); } public Builder setReservedForOrderLine(@Nullable final OrderLineId orderLineId) { orderLineReservation = orderLineId; huReserved = orderLineId != null; return this; } /** * @param currentRow the row that is currently constructed using this builder */ private ImmutableMultimap<OrderLineId, HUEditorRow> prepareIncludedOrderLineReservations(@NonNull final HUEditorRow currentRow) { final ImmutableMultimap.Builder<OrderLineId, HUEditorRow> includedOrderLineReservationsBuilder = ImmutableMultimap.builder(); for (final HUEditorRow includedRow : buildIncludedRows()) { includedOrderLineReservationsBuilder.putAll(includedRow.getIncludedOrderLineReservations()); } if (orderLineReservation != null) { includedOrderLineReservationsBuilder.put(orderLineReservation, currentRow); } return includedOrderLineReservationsBuilder.build(); } } @lombok.Builder @lombok.Value public static class HUEditorRowHierarchy { @NonNull private final HUEditorRow cuRow; @Nullable private final HUEditorRow parentRow; @Nullable private final HUEditorRow topLevelRow; } }