diff --git a/src/main/java/de/metas/ui/web/debug/DebugRestController.java b/src/main/java/de/metas/ui/web/debug/DebugRestController.java index fa6447b1a..758667d95 100644 --- a/src/main/java/de/metas/ui/web/debug/DebugRestController.java +++ b/src/main/java/de/metas/ui/web/debug/DebugRestController.java @@ -51,6 +51,7 @@ import de.metas.ui.web.session.UserSession; import de.metas.ui.web.view.IViewsRepository; import de.metas.ui.web.view.ViewResult; +import de.metas.ui.web.view.descriptor.annotation.ViewColumnHelper; import de.metas.ui.web.view.json.JSONViewResult; import de.metas.ui.web.websocket.WebsocketSender; import de.metas.ui.web.websocket.WebsocketSender.WebsocketEvent; @@ -121,6 +122,7 @@ public void cacheReset() documentCollection.cacheReset(); menuTreeRepo.cacheReset(); processesController.cacheReset(); + ViewColumnHelper.cacheReset(); Services.get(IUserRolePermissionsDAO.class).resetLocalCache(); System.gc(); diff --git a/src/main/java/de/metas/ui/web/picking/PickingRow.java b/src/main/java/de/metas/ui/web/picking/PickingRow.java new file mode 100644 index 000000000..8e0540559 --- /dev/null +++ b/src/main/java/de/metas/ui/web/picking/PickingRow.java @@ -0,0 +1,149 @@ +package de.metas.ui.web.picking; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import de.metas.inoutcandidate.model.I_M_Packageable_V; +import de.metas.ui.web.exceptions.EntityNotFoundException; +import de.metas.ui.web.view.IViewRow; +import de.metas.ui.web.view.IViewRowAttributes; +import de.metas.ui.web.view.IViewRowType; +import de.metas.ui.web.view.descriptor.annotation.ViewColumn; +import de.metas.ui.web.view.descriptor.annotation.ViewColumnHelper; +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.descriptor.DocumentFieldWidgetType; +import lombok.Builder; +import lombok.NonNull; +import lombok.ToString; + +/* + * #%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 + * . + * #L% + */ + +@ToString +public final class PickingRow implements IViewRow +{ + private final DocumentId id; + private final IViewRowType type; + private final boolean processed; + private final DocumentPath documentPath; + + @ViewColumn(widgetType = DocumentFieldWidgetType.Lookup, captionKey = I_M_Packageable_V.COLUMNNAME_M_Warehouse_ID, seqNo = 10) + private final LookupValue warehouse; + @ViewColumn(widgetType = DocumentFieldWidgetType.Lookup, captionKey = I_M_Packageable_V.COLUMNNAME_M_Product_ID, seqNo = 20) + private final LookupValue product; + @ViewColumn(widgetType = DocumentFieldWidgetType.Quantity, captionKey = I_M_Packageable_V.COLUMNNAME_QtyToDeliver, seqNo = 30) + private final BigDecimal qtyToDeliver; + @ViewColumn(widgetType = DocumentFieldWidgetType.DateTime, captionKey = I_M_Packageable_V.COLUMNNAME_DeliveryDate, seqNo = 40) + private final java.util.Date deliveryDate; + @ViewColumn(widgetType = DocumentFieldWidgetType.DateTime, captionKey = I_M_Packageable_V.COLUMNNAME_PreparationDate, seqNo = 50) + private final java.util.Date preparationDate; + + private transient ImmutableMap _fieldNameAndJsonValues; + + @Builder + private PickingRow(@NonNull final DocumentId id, + final IViewRowType type, + final boolean processed, + @NonNull final DocumentPath documentPath, + // + final LookupValue warehouse, + final LookupValue product, + final BigDecimal qtyToDeliver, + final Date deliveryDate, + final Date preparationDate) + { + this.id = id; + this.type = type; + this.processed = processed; + this.documentPath = documentPath; + this.warehouse = warehouse; + this.product = product; + this.qtyToDeliver = qtyToDeliver; + this.deliveryDate = deliveryDate; + this.preparationDate = preparationDate; + } + + @Override + public DocumentId getId() + { + return id; + } + + @Override + public IViewRowType getType() + { + return type; + } + + @Override + public boolean isProcessed() + { + return processed; + } + + @Override + public DocumentPath getDocumentPath() + { + return documentPath; + } + + @Override + public Map getFieldNameAndJsonValues() + { + if (_fieldNameAndJsonValues == null) + { + _fieldNameAndJsonValues = ViewColumnHelper.extractJsonMap(this); + } + return _fieldNameAndJsonValues; + } + + @Override + public List getIncludedRows() + { + return ImmutableList.of(); + } + + @Override + public boolean hasAttributes() + { + return false; + } + + @Override + public IViewRowAttributes getAttributes() throws EntityNotFoundException + { + throw new EntityNotFoundException("Row does not support attributes"); + } + + @Override + public boolean hasIncludedView() + { + return true; + } +} diff --git a/src/main/java/de/metas/ui/web/picking/PickingSlotRow.java b/src/main/java/de/metas/ui/web/picking/PickingSlotRow.java new file mode 100644 index 000000000..77158dbb0 --- /dev/null +++ b/src/main/java/de/metas/ui/web/picking/PickingSlotRow.java @@ -0,0 +1,144 @@ +package de.metas.ui.web.picking; + +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import de.metas.handlingunits.model.I_M_PickingSlot; +import de.metas.ui.web.exceptions.EntityNotFoundException; +import de.metas.ui.web.view.IViewRow; +import de.metas.ui.web.view.IViewRowAttributes; +import de.metas.ui.web.view.IViewRowType; +import de.metas.ui.web.view.descriptor.annotation.ViewColumn; +import de.metas.ui.web.view.descriptor.annotation.ViewColumnHelper; +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.descriptor.DocumentFieldWidgetType; +import lombok.Builder; +import lombok.NonNull; +import lombok.ToString; + +/* + * #%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 + * . + * #L% + */ + +@ToString +public final class PickingSlotRow implements IViewRow +{ + private final DocumentId id; + private final IViewRowType type; + private final boolean processed; + private final DocumentPath documentPath; + + @ViewColumn(widgetType = DocumentFieldWidgetType.Text, captionKey = I_M_PickingSlot.COLUMNNAME_PickingSlot, seqNo = 10) + private final String name; + @ViewColumn(widgetType = DocumentFieldWidgetType.Lookup, captionKey = I_M_PickingSlot.COLUMNNAME_M_Warehouse_ID, seqNo = 20) + private final LookupValue warehouse; + @ViewColumn(widgetType = DocumentFieldWidgetType.Lookup, captionKey = I_M_PickingSlot.COLUMNNAME_C_BPartner_ID, seqNo = 30) + private final LookupValue bpartner; + @ViewColumn(widgetType = DocumentFieldWidgetType.Lookup, captionKey = I_M_PickingSlot.COLUMNNAME_C_BPartner_Location_ID, seqNo = 40) + private final LookupValue bpartnerLocation; + + private transient ImmutableMap _fieldNameAndJsonValues; + + @Builder + private PickingSlotRow(@NonNull final DocumentId id, + final IViewRowType type, + final boolean processed, + @NonNull final DocumentPath documentPath, + // + final String name, + final LookupValue warehouse, + final LookupValue bpartner, + final LookupValue bpartnerLocation) + { + this.id = id; + this.type = type; + this.processed = processed; + this.documentPath = documentPath; + + this.name = name; + this.warehouse = warehouse; + this.bpartner = bpartner; + this.bpartnerLocation = bpartnerLocation; + } + + @Override + public DocumentId getId() + { + return id; + } + + @Override + public IViewRowType getType() + { + return type; + } + + @Override + public boolean isProcessed() + { + return processed; + } + + @Override + public DocumentPath getDocumentPath() + { + return documentPath; + } + + @Override + public Map getFieldNameAndJsonValues() + { + if (_fieldNameAndJsonValues == null) + { + _fieldNameAndJsonValues = ViewColumnHelper.extractJsonMap(this); + } + return _fieldNameAndJsonValues; + } + + @Override + public List getIncludedRows() + { + return ImmutableList.of(); + } + + @Override + public boolean hasAttributes() + { + return false; + } + + @Override + public IViewRowAttributes getAttributes() throws EntityNotFoundException + { + throw new EntityNotFoundException("Row does not support attributes"); + } + + @Override + public boolean hasIncludedView() + { + return false; + } +} diff --git a/src/main/java/de/metas/ui/web/picking/PickingSlotView.java b/src/main/java/de/metas/ui/web/picking/PickingSlotView.java new file mode 100644 index 000000000..8d2898578 --- /dev/null +++ b/src/main/java/de/metas/ui/web/picking/PickingSlotView.java @@ -0,0 +1,216 @@ +package de.metas.ui.web.picking; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import org.adempiere.util.lang.impl.TableRecordReference; +import org.compiere.util.Evaluatee; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; + +import de.metas.i18n.ITranslatableString; +import de.metas.inoutcandidate.model.I_M_Packageable_V; +import de.metas.ui.web.document.filter.DocumentFilter; +import de.metas.ui.web.exceptions.EntityNotFoundException; +import de.metas.ui.web.view.IView; +import de.metas.ui.web.view.IViewRow; +import de.metas.ui.web.view.ViewId; +import de.metas.ui.web.view.ViewResult; +import de.metas.ui.web.view.json.JSONViewDataType; +import de.metas.ui.web.window.datatypes.DocumentId; +import de.metas.ui.web.window.datatypes.DocumentIdsSelection; +import de.metas.ui.web.window.datatypes.DocumentPath; +import de.metas.ui.web.window.datatypes.LookupValuesList; +import de.metas.ui.web.window.model.DocumentQueryOrderBy; +import lombok.Builder; +import lombok.NonNull; + +/* + * #%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 + * . + * #L% + */ + +public class PickingSlotView implements IView +{ + private final ViewId viewId; + private ITranslatableString description; + private final Map rows; + + @Builder + private PickingSlotView(@NonNull final ViewId viewId, + final ITranslatableString description, + final List rows) + { + this.viewId = viewId; + this.description = description != null ? description : ITranslatableString.empty(); + this.rows = Maps.uniqueIndex(rows, PickingSlotRow::getId); + } + + @Override + public ViewId getViewId() + { + return viewId; + } + + @Override + public ITranslatableString getDescription() + { + return description; + } + + @Override + public JSONViewDataType getViewType() + { + return JSONViewDataType.grid; + } + + @Override + public Set getReferencingDocumentPaths() + { + return ImmutableSet.of(); + } + + @Override + public String getTableName() + { + return I_M_Packageable_V.Table_Name; + } + + @Override + public ViewId getParentViewId() + { + return null; + } + + @Override + public long size() + { + return rows.size(); + } + + @Override + public void close() + { + } + + @Override + public int getQueryLimit() + { + return -1; + } + + @Override + public boolean isQueryLimitHit() + { + return false; + } + + @Override + public ViewResult getPage(final int firstRow, final int pageLength, final List orderBys) + { + final List pageRows = rows.values().stream() + .skip(firstRow >= 0 ? firstRow : 0) + .limit(pageLength > 0 ? pageLength : 30) + .collect(ImmutableList.toImmutableList()); + + return ViewResult.ofViewAndPage(this, firstRow, pageLength, orderBys, pageRows); + } + + @Override + public PickingSlotRow getById(final DocumentId rowId) throws EntityNotFoundException + { + final PickingSlotRow row = rows.get(rowId); + if (row == null) + { + throw new EntityNotFoundException("Row not found").setParameter("rowId", rowId); + } + return row; + } + + @Override + public LookupValuesList getFilterParameterDropdown(final String filterId, final String filterParameterName, final Evaluatee ctx) + { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public LookupValuesList getFilterParameterTypeahead(final String filterId, final String filterParameterName, final String query, final Evaluatee ctx) + { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public List getStickyFilters() + { + // TODO Auto-generated method stub + return ImmutableList.of(); + } + + @Override + public List getFilters() + { + return ImmutableList.of(); + } + + @Override + public List getDefaultOrderBys() + { + return ImmutableList.of(); + } + + @Override + public String getSqlWhereClause(final DocumentIdsSelection rowIds) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean hasAttributesSupport() + { + return false; + } + + @Override + public List retrieveModelsByIds(final DocumentIdsSelection rowIds, final Class modelClass) + { + throw new UnsupportedOperationException(); + } + + @Override + public Stream streamByIds(final DocumentIdsSelection rowIds) + { + return rowIds.stream().map(this::getById); + } + + @Override + public void notifyRecordsChanged(final Set recordRefs) + { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/java/de/metas/ui/web/picking/PickingSlotViewFactory.java b/src/main/java/de/metas/ui/web/picking/PickingSlotViewFactory.java new file mode 100644 index 000000000..3290b960d --- /dev/null +++ b/src/main/java/de/metas/ui/web/picking/PickingSlotViewFactory.java @@ -0,0 +1,79 @@ +package de.metas.ui.web.picking; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.common.collect.ImmutableSet; + +import de.metas.ui.web.document.filter.DocumentFilterDescriptor; +import de.metas.ui.web.view.CreateViewRequest; +import de.metas.ui.web.view.IViewFactory; +import de.metas.ui.web.view.ViewFactory; +import de.metas.ui.web.view.ViewId; +import de.metas.ui.web.view.descriptor.ViewLayout; +import de.metas.ui.web.view.json.JSONViewDataType; +import de.metas.ui.web.window.datatypes.DocumentId; +import de.metas.ui.web.window.datatypes.WindowId; + +/* + * #%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 + * . + * #L% + */ + +@ViewFactory(windowId = PickingConstants.WINDOWID_PickingSlotView_String, viewTypes = { JSONViewDataType.grid, JSONViewDataType.includedView }) +public class PickingSlotViewFactory implements IViewFactory +{ + @Autowired + private PickingSlotViewRepository pickingSlotRepo; + + @Override + public ViewLayout getViewLayout(final WindowId windowId, final JSONViewDataType viewDataType) + { + // TODO: cache it + + return ViewLayout.builder() + .setWindowId(PickingConstants.WINDOWID_PickingSlotView) + .setCaption("Picking slots") + .addElementsFromViewRowClass(PickingSlotRow.class) + .build(); + } + + @Override + public Collection getViewFilterDescriptors(final WindowId windowId, final JSONViewDataType viewDataType) + { + return getViewLayout(windowId, viewDataType).getFilters(); + } + + @Override + public PickingSlotView createView(final CreateViewRequest request) + { + final Set rowIds = request.getFilterOnlyIds().stream().map(DocumentId::of).collect(ImmutableSet.toImmutableSet()); + final List rows = pickingSlotRepo.retrieveRowsByIds(rowIds); + + return PickingSlotView.builder() + .viewId(ViewId.random(request.getWindowId())) + .rows(rows) + .build(); + } + +} diff --git a/src/main/java/de/metas/ui/web/picking/PickingSlotViewRepository.java b/src/main/java/de/metas/ui/web/picking/PickingSlotViewRepository.java new file mode 100644 index 000000000..f280c7fb6 --- /dev/null +++ b/src/main/java/de/metas/ui/web/picking/PickingSlotViewRepository.java @@ -0,0 +1,114 @@ +package de.metas.ui.web.picking; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.adempiere.ad.trx.api.ITrx; +import org.adempiere.util.Services; +import org.compiere.util.DisplayType; +import org.compiere.util.Env; +import org.springframework.stereotype.Component; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import de.metas.picking.api.IPickingSlotDAO; +import de.metas.picking.model.I_M_PickingSlot; +import de.metas.ui.web.view.ViewRow.DefaultRowType; +import de.metas.ui.web.window.datatypes.DocumentId; +import de.metas.ui.web.window.datatypes.DocumentPath; +import de.metas.ui.web.window.descriptor.DocumentFieldWidgetType; +import de.metas.ui.web.window.descriptor.LookupDescriptorProvider.LookupScope; +import de.metas.ui.web.window.descriptor.sql.SqlLookupDescriptor; +import de.metas.ui.web.window.model.lookup.LookupDataSource; +import de.metas.ui.web.window.model.lookup.LookupDataSourceFactory; + +/* + * #%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 + * . + * #L% + */ + +@Component +public class PickingSlotViewRepository +{ + private final LookupDataSource warehouseLookup; + private final LookupDataSource bpartnerLookup; + private final LookupDataSource bpartnerLocationLookup; + + public PickingSlotViewRepository() + { + warehouseLookup = LookupDataSourceFactory.instance.getLookupDataSource(SqlLookupDescriptor.builder() + .setColumnName(I_M_PickingSlot.COLUMNNAME_M_Warehouse_ID) + .setDisplayType(DisplayType.Search) + .setWidgetType(DocumentFieldWidgetType.Lookup) + .buildProvider() + .provideForScope(LookupScope.DocumentField)); + bpartnerLookup = LookupDataSourceFactory.instance.getLookupDataSource(SqlLookupDescriptor.builder() + .setColumnName(I_M_PickingSlot.COLUMNNAME_C_BPartner_ID) + .setDisplayType(DisplayType.Search) + .setWidgetType(DocumentFieldWidgetType.Lookup) + .buildProvider() + .provideForScope(LookupScope.DocumentField)); + bpartnerLocationLookup = LookupDataSourceFactory.instance.getLookupDataSource(SqlLookupDescriptor.builder() + .setColumnName(I_M_PickingSlot.COLUMNNAME_C_BPartner_Location_ID) + .setDisplayType(DisplayType.Search) + .setWidgetType(DocumentFieldWidgetType.Lookup) + .buildProvider() + .provideForScope(LookupScope.DocumentField)); + } + + public List retrieveRowsByIds(final Collection rowIds) + { + final Set pickingSlotIds = rowIds.stream().map(DocumentId::toInt).collect(ImmutableSet.toImmutableSet()); + return Services.get(IPickingSlotDAO.class).retrievePickingSlotsByIds(pickingSlotIds) + .stream() + .map(pickingSlotPO -> createPickingSlotRow(pickingSlotPO)) + .collect(ImmutableList.toImmutableList()); + } + + public Set retrieveAllRowIds() + { + return Services.get(IPickingSlotDAO.class).retrievePickingSlots(Env.getCtx(), ITrx.TRXNAME_ThreadInherited) + .stream() + .map(I_M_PickingSlot::getM_PickingSlot_ID) + .collect(ImmutableSet.toImmutableSet()); + } + + + private PickingSlotRow createPickingSlotRow(final I_M_PickingSlot pickingSlotPO) + { + final DocumentId rowId = DocumentId.of(pickingSlotPO.getM_PickingSlot_ID()); + final DocumentPath documentPath = DocumentPath.rootDocumentPath(PickingConstants.WINDOWID_PickingSlotView, rowId); + return PickingSlotRow.builder() + .documentPath(documentPath) + .id(rowId) + .type(DefaultRowType.Row) + .processed(false) + // + .name(pickingSlotPO.getPickingSlot()) + .warehouse(warehouseLookup.findById(pickingSlotPO.getM_Warehouse_ID())) + .bpartner(bpartnerLookup.findById(pickingSlotPO.getC_BPartner_ID())) + .bpartnerLocation(bpartnerLocationLookup.findById(pickingSlotPO.getC_BPartner_Location_ID())) + // + .build(); + } + +} diff --git a/src/main/java/de/metas/ui/web/picking/PickingView.java b/src/main/java/de/metas/ui/web/picking/PickingView.java new file mode 100644 index 000000000..699b5450a --- /dev/null +++ b/src/main/java/de/metas/ui/web/picking/PickingView.java @@ -0,0 +1,256 @@ +package de.metas.ui.web.picking; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import org.adempiere.ad.dao.IQueryBL; +import org.adempiere.exceptions.AdempiereException; +import org.adempiere.model.InterfaceWrapperHelper; +import org.adempiere.util.Services; +import org.adempiere.util.lang.impl.TableRecordReference; +import org.compiere.Adempiere; +import org.compiere.util.Evaluatee; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; + +import de.metas.i18n.ITranslatableString; +import de.metas.inoutcandidate.model.I_M_Packageable_V; +import de.metas.ui.web.document.filter.DocumentFilter; +import de.metas.ui.web.exceptions.EntityNotFoundException; +import de.metas.ui.web.process.ProcessInstanceResult.CreateAndOpenIncludedViewAction; +import de.metas.ui.web.process.view.ViewAction; +import de.metas.ui.web.view.CreateViewRequest; +import de.metas.ui.web.view.IView; +import de.metas.ui.web.view.IViewRow; +import de.metas.ui.web.view.ViewId; +import de.metas.ui.web.view.ViewResult; +import de.metas.ui.web.view.json.JSONViewDataType; +import de.metas.ui.web.window.datatypes.DocumentId; +import de.metas.ui.web.window.datatypes.DocumentIdsSelection; +import de.metas.ui.web.window.datatypes.DocumentPath; +import de.metas.ui.web.window.datatypes.LookupValuesList; +import de.metas.ui.web.window.model.DocumentQueryOrderBy; +import lombok.Builder; +import lombok.NonNull; + +/* + * #%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 + * . + * #L% + */ + +public class PickingView implements IView +{ + private final ViewId viewId; + private final ITranslatableString description; + private final Map rows; + + @Builder + private PickingView(@NonNull final ViewId viewId, + final ITranslatableString description, + final List rows) + { + this.viewId = viewId; + this.description = description != null ? description : ITranslatableString.empty(); + this.rows = Maps.uniqueIndex(rows, PickingRow::getId); + } + + @Override + public ViewId getViewId() + { + return viewId; + } + + @Override + public ITranslatableString getDescription() + { + return description; + } + + @Override + public JSONViewDataType getViewType() + { + return JSONViewDataType.grid; + } + + @Override + public Set getReferencingDocumentPaths() + { + return ImmutableSet.of(); + } + + @Override + public String getTableName() + { + return I_M_Packageable_V.Table_Name; + } + + @Override + public ViewId getParentViewId() + { + return null; + } + + @Override + public long size() + { + return rows.size(); + } + + @Override + public void close() + { + } + + @Override + public int getQueryLimit() + { + return -1; + } + + @Override + public boolean isQueryLimitHit() + { + return false; + } + + @Override + public ViewResult getPage(final int firstRow, final int pageLength, final List orderBys) + { + final List pageRows = rows.values().stream() + .skip(firstRow >= 0 ? firstRow : 0) + .limit(pageLength > 0 ? pageLength : 30) + .collect(ImmutableList.toImmutableList()); + + return ViewResult.ofViewAndPage(this, firstRow, pageLength, orderBys, pageRows); + } + + @Override + public PickingRow getById(final DocumentId rowId) throws EntityNotFoundException + { + final PickingRow row = rows.get(rowId); + if (row == null) + { + throw new EntityNotFoundException("Row not found").setParameter("rowId", rowId); + } + return row; + } + + @Override + public LookupValuesList getFilterParameterDropdown(final String filterId, final String filterParameterName, final Evaluatee ctx) + { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public LookupValuesList getFilterParameterTypeahead(final String filterId, final String filterParameterName, final String query, final Evaluatee ctx) + { + // TODO Auto-generated method stub + throw new UnsupportedOperationException(); + } + + @Override + public List getStickyFilters() + { + // TODO Auto-generated method stub + return ImmutableList.of(); + } + + @Override + public List getFilters() + { + return ImmutableList.of(); + } + + @Override + public List getDefaultOrderBys() + { + return ImmutableList.of(); + } + + @Override + public String getSqlWhereClause(final DocumentIdsSelection rowIds) + { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean hasAttributesSupport() + { + return false; + } + + @Override + public List retrieveModelsByIds(final DocumentIdsSelection rowIds, final Class modelClass) + { + final Set shipmentScheduleIds = rowIds.toIntSet(); + if (shipmentScheduleIds.isEmpty()) + { + return ImmutableList.of(); + } + + final List packables = Services.get(IQueryBL.class) + .createQueryBuilder(I_M_Packageable_V.class) + .addInArrayFilter(I_M_Packageable_V.COLUMN_M_ShipmentSchedule_ID, shipmentScheduleIds) + .create() + .list(I_M_Packageable_V.class); + return InterfaceWrapperHelper.createList(packables, modelClass); + } + + @Override + public Stream streamByIds(final DocumentIdsSelection rowIds) + { + return rowIds.stream().map(this::getById); + } + + @Override + public void notifyRecordsChanged(final Set recordRefs) + { + // TODO Auto-generated method stub + + } + + @ViewAction(caption = "picking slots", defaultAction = true) + public CreateAndOpenIncludedViewAction openPickingSlots(final PickingView pickingView, final DocumentIdsSelection selectedRowIds) + { + if (!selectedRowIds.isSingleDocumentId()) + { + throw new AdempiereException("Not a single selection"); + } + + // TODO: fetch the picking slots eligible for selected picking row + final DocumentId pickingRowId = selectedRowIds.getSingleDocumentId(); + final PickingRow pickingRow = pickingView.getById(pickingRowId); + final PickingSlotViewRepository pickingSlotRepo = Adempiere.getBean(PickingSlotViewRepository.class); + final Set pickingSlotRowIds = pickingSlotRepo.retrieveAllRowIds(); + + final CreateViewRequest createViewRequest = CreateViewRequest.builder(PickingConstants.WINDOWID_PickingSlotView, JSONViewDataType.includedView) + .setParentViewId(viewId) + .setFilterOnlyIds(pickingSlotRowIds) + .build(); + return CreateAndOpenIncludedViewAction.of(createViewRequest); + } + +} diff --git a/src/main/java/de/metas/ui/web/picking/PickingViewFactory.java b/src/main/java/de/metas/ui/web/picking/PickingViewFactory.java new file mode 100644 index 000000000..1365aebc8 --- /dev/null +++ b/src/main/java/de/metas/ui/web/picking/PickingViewFactory.java @@ -0,0 +1,97 @@ +package de.metas.ui.web.picking; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.springframework.beans.factory.annotation.Autowired; + +import com.google.common.collect.ImmutableSet; + +import de.metas.i18n.ITranslatableString; +import de.metas.ui.web.document.filter.DocumentFilterDescriptor; +import de.metas.ui.web.view.CreateViewRequest; +import de.metas.ui.web.view.IView; +import de.metas.ui.web.view.IViewFactory; +import de.metas.ui.web.view.ViewFactory; +import de.metas.ui.web.view.ViewId; +import de.metas.ui.web.view.descriptor.ViewLayout; +import de.metas.ui.web.view.json.JSONViewDataType; +import de.metas.ui.web.window.datatypes.DocumentId; +import de.metas.ui.web.window.datatypes.WindowId; +import de.metas.ui.web.window.descriptor.factory.standard.LayoutFactory; + +/* + * #%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 + * . + * #L% + */ + +@ViewFactory(windowId = PickingConstants.WINDOWID_PickingView_String, viewTypes = { JSONViewDataType.grid, JSONViewDataType.includedView }) +public class PickingViewFactory implements IViewFactory +{ + @Autowired + private PickingViewRepository pickingViewRepo; + + @Override + public ViewLayout getViewLayout(final WindowId windowId, final JSONViewDataType viewDataType) + { + // TODO: cache it + + return ViewLayout.builder() + .setWindowId(PickingConstants.WINDOWID_PickingView) + .setCaption("Picking") + .setEmptyResultText(LayoutFactory.HARDCODED_TAB_EMPTY_RESULT_TEXT) + .setEmptyResultHint(LayoutFactory.HARDCODED_TAB_EMPTY_RESULT_HINT) + // + .setHasAttributesSupport(false) + .setHasTreeSupport(false) + .setHasIncludedViewSupport(true) + // + .addElementsFromViewRowClass(PickingRow.class) + // + .build(); + } + + @Override + public Collection getViewFilterDescriptors(final WindowId windowId, final JSONViewDataType viewDataType) + { + return getViewLayout(windowId, viewDataType).getFilters(); + } + + @Override + public IView createView(final CreateViewRequest request) + { + final WindowId windowId = request.getWindowId(); + if (!PickingConstants.WINDOWID_PickingView.equals(windowId)) + { + throw new IllegalArgumentException("Invalid request's windowId: " + request); + } + + final Set rowIds = request.getFilterOnlyIds().stream().map(DocumentId::of).collect(ImmutableSet.toImmutableSet()); + final List rows = pickingViewRepo.retrieveRowsByIds(rowIds); + + return PickingView.builder() + .viewId(ViewId.random(PickingConstants.WINDOWID_PickingView)) + .description(ITranslatableString.empty()) + .rows(rows) + .build(); + } + +} diff --git a/src/main/java/de/metas/ui/web/picking/PickingViewRepository.java b/src/main/java/de/metas/ui/web/picking/PickingViewRepository.java new file mode 100644 index 000000000..9524629c3 --- /dev/null +++ b/src/main/java/de/metas/ui/web/picking/PickingViewRepository.java @@ -0,0 +1,106 @@ +package de.metas.ui.web.picking; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.adempiere.ad.dao.IQueryBL; +import org.adempiere.util.Services; +import org.compiere.util.DisplayType; +import org.springframework.stereotype.Component; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + +import de.metas.inoutcandidate.model.I_M_Packageable_V; +import de.metas.ui.web.view.ViewRow.DefaultRowType; +import de.metas.ui.web.window.datatypes.DocumentId; +import de.metas.ui.web.window.datatypes.DocumentPath; +import de.metas.ui.web.window.descriptor.DocumentFieldWidgetType; +import de.metas.ui.web.window.descriptor.LookupDescriptorProvider.LookupScope; +import de.metas.ui.web.window.descriptor.sql.SqlLookupDescriptor; +import de.metas.ui.web.window.model.lookup.LookupDataSource; +import de.metas.ui.web.window.model.lookup.LookupDataSourceFactory; + +/* + * #%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 + * . + * #L% + */ + +@Component +public class PickingViewRepository +{ + private final LookupDataSource warehouseLookup; + private final LookupDataSource productLookup; + + public PickingViewRepository() + { + warehouseLookup = LookupDataSourceFactory.instance.getLookupDataSource(SqlLookupDescriptor.builder() + .setColumnName(I_M_Packageable_V.COLUMNNAME_M_Warehouse_ID) + .setDisplayType(DisplayType.Search) + .setWidgetType(DocumentFieldWidgetType.Lookup) + .buildProvider() + .provideForScope(LookupScope.DocumentField)); + + productLookup = LookupDataSourceFactory.instance.getLookupDataSource(SqlLookupDescriptor.builder() + .setColumnName(I_M_Packageable_V.COLUMNNAME_M_Product_ID) + .setDisplayType(DisplayType.Search) + .setWidgetType(DocumentFieldWidgetType.Lookup) + .buildProvider() + .provideForScope(LookupScope.DocumentField)); + } + + public List retrieveRowsByIds(final Collection rowIds) + { + final Set shipmentScheduleIds = rowIds.stream().map(DocumentId::toInt).collect(ImmutableSet.toImmutableSet()); + if (shipmentScheduleIds.isEmpty()) + { + return ImmutableList.of(); + } + + return Services.get(IQueryBL.class) + .createQueryBuilder(I_M_Packageable_V.class) + .addInArrayFilter(I_M_Packageable_V.COLUMN_M_ShipmentSchedule_ID, shipmentScheduleIds) + .create() + .stream(I_M_Packageable_V.class) + .map(packageable -> createPickingRow(packageable)) + .collect(ImmutableList.toImmutableList()); + + } + + private PickingRow createPickingRow(final I_M_Packageable_V packageable) + { + final DocumentId rowId = DocumentId.of(packageable.getM_ShipmentSchedule_ID()); + final DocumentPath documentPath = DocumentPath.rootDocumentPath(PickingConstants.WINDOWID_PickingView, rowId); + return PickingRow.builder() + .documentPath(documentPath) + .id(rowId) + .type(DefaultRowType.Row) + .processed(false) + // + .warehouse(warehouseLookup.findById(packageable.getM_Warehouse_ID())) + .product(productLookup.findById(packageable.getM_Product_ID())) + .deliveryDate(packageable.getDeliveryDate()) + .preparationDate(packageable.getPreparationDate()) + .qtyToDeliver(packageable.getQtyToDeliver()) + // + .build(); + } +} diff --git a/src/main/java/de/metas/ui/web/picking/process/WEBUI_M_Packageable_StartPicking.java b/src/main/java/de/metas/ui/web/picking/process/WEBUI_M_Packageable_StartPicking.java new file mode 100644 index 000000000..7accdd8a4 --- /dev/null +++ b/src/main/java/de/metas/ui/web/picking/process/WEBUI_M_Packageable_StartPicking.java @@ -0,0 +1,71 @@ +package de.metas.ui.web.picking.process; + +import java.util.List; + +import org.adempiere.exceptions.AdempiereException; +import org.adempiere.util.lang.impl.TableRecordReference; + +import com.google.common.collect.ImmutableList; + +import de.metas.inoutcandidate.model.I_M_Packageable_V; +import de.metas.process.ProcessPreconditionsResolution; +import de.metas.ui.web.picking.PickingConstants; +import de.metas.ui.web.process.adprocess.ViewBasedProcessTemplate; +import de.metas.ui.web.view.IViewRow; +import de.metas.ui.web.window.datatypes.DocumentIdsSelection; + +/* + * #%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 + * . + * #L% + */ + +public class WEBUI_M_Packageable_StartPicking extends ViewBasedProcessTemplate +{ + @Override + protected ProcessPreconditionsResolution checkPreconditionsApplicable() + { + if (getSelectedDocumentIds().isEmpty()) + { + return ProcessPreconditionsResolution.rejectBecauseNoSelection(); + } + + return ProcessPreconditionsResolution.accept(); + } + + @Override + protected String doIt() throws Exception + { + final DocumentIdsSelection selectedRowIds = getSelectedDocumentIds(); + final List rowIds = getView().getByIds(selectedRowIds) + .stream() + .flatMap(selectedRow -> selectedRow.getIncludedRows().stream()) + .map(IViewRow::getId) + .map(rowId -> rowId.removePrefixAndConvertToInt("D")) // FIXME: hardcoded + .collect(ImmutableList.toImmutableList()); + if (rowIds.isEmpty()) + { + throw new AdempiereException("@NoSelection@"); + } + + getResult().setRecordsToOpen(TableRecordReference.ofRecordIds(I_M_Packageable_V.Table_Name, rowIds), PickingConstants.WINDOWID_PickingView.toInt()); + return MSG_OK; + } + +} diff --git a/src/main/java/de/metas/ui/web/process/IProcessInstanceController.java b/src/main/java/de/metas/ui/web/process/IProcessInstanceController.java index 40352da44..e5bfa691b 100644 --- a/src/main/java/de/metas/ui/web/process/IProcessInstanceController.java +++ b/src/main/java/de/metas/ui/web/process/IProcessInstanceController.java @@ -3,6 +3,7 @@ import java.util.Collection; import java.util.List; +import de.metas.ui.web.view.IViewsRepository; import de.metas.ui.web.window.datatypes.DocumentId; import de.metas.ui.web.window.datatypes.LookupValuesList; import de.metas.ui.web.window.datatypes.json.JSONDocumentChangedEvent; @@ -47,7 +48,7 @@ public interface IProcessInstanceController { DocumentId getInstanceId(); - ProcessInstanceResult startProcess(); + ProcessInstanceResult startProcess(IViewsRepository viewsRepo); /** * @return execution result or throws exception if the process was not already executed diff --git a/src/main/java/de/metas/ui/web/process/ProcessInstanceResult.java b/src/main/java/de/metas/ui/web/process/ProcessInstanceResult.java index 5312e36f1..ce7252843 100644 --- a/src/main/java/de/metas/ui/web/process/ProcessInstanceResult.java +++ b/src/main/java/de/metas/ui/web/process/ProcessInstanceResult.java @@ -9,11 +9,13 @@ import com.google.common.base.MoreObjects; +import de.metas.ui.web.view.CreateViewRequest; import de.metas.ui.web.view.ViewId; import de.metas.ui.web.window.datatypes.DocumentId; import de.metas.ui.web.window.datatypes.DocumentIdsSelection; import de.metas.ui.web.window.datatypes.DocumentPath; import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NonNull; import lombok.Setter; @@ -165,6 +167,14 @@ public static class OpenIncludedViewAction implements ResultAction private final ViewId viewId; } + @lombok.Value + @AllArgsConstructor(staticName = "of") + public static class CreateAndOpenIncludedViewAction implements ResultAction + { + @NonNull + private final CreateViewRequest createViewRequest; + } + @lombok.Value @lombok.Builder public static final class OpenSingleDocument implements ResultAction diff --git a/src/main/java/de/metas/ui/web/process/ProcessRestController.java b/src/main/java/de/metas/ui/web/process/ProcessRestController.java index ef54fde30..a55e343db 100644 --- a/src/main/java/de/metas/ui/web/process/ProcessRestController.java +++ b/src/main/java/de/metas/ui/web/process/ProcessRestController.java @@ -247,7 +247,7 @@ public JSONProcessInstanceResult startProcess( .execute(() -> { final IDocumentChangesCollector changesCollector = NullDocumentChangesCollector.instance; return instancesRepository.forProcessInstanceWritable(pinstanceId, changesCollector, processInstance -> { - final ProcessInstanceResult result = processInstance.startProcess(); + final ProcessInstanceResult result = processInstance.startProcess(viewsRepo); return JSONProcessInstanceResult.of(result); }); }); diff --git a/src/main/java/de/metas/ui/web/process/ViewAsPreconditionsContext.java b/src/main/java/de/metas/ui/web/process/ViewAsPreconditionsContext.java index 5ffa5378e..76ae40539 100644 --- a/src/main/java/de/metas/ui/web/process/ViewAsPreconditionsContext.java +++ b/src/main/java/de/metas/ui/web/process/ViewAsPreconditionsContext.java @@ -111,7 +111,7 @@ public T getView(final Class viewType) @Override public int getAD_Window_ID() { - return windowId.toInt(); + return windowId.toIntOr(-1); } @Override diff --git a/src/main/java/de/metas/ui/web/process/adprocess/ADProcessInstanceController.java b/src/main/java/de/metas/ui/web/process/adprocess/ADProcessInstanceController.java index 05cd1eefd..f3226126a 100644 --- a/src/main/java/de/metas/ui/web/process/adprocess/ADProcessInstanceController.java +++ b/src/main/java/de/metas/ui/web/process/adprocess/ADProcessInstanceController.java @@ -241,7 +241,7 @@ private boolean isExecuted() } @Override - public ProcessInstanceResult startProcess() + public ProcessInstanceResult startProcess(final IViewsRepository viewsRepo) { assertNotExecuted(); diff --git a/src/main/java/de/metas/ui/web/process/view/ViewAction.java b/src/main/java/de/metas/ui/web/process/view/ViewAction.java index aa5c86a36..ffc13e089 100644 --- a/src/main/java/de/metas/ui/web/process/view/ViewAction.java +++ b/src/main/java/de/metas/ui/web/process/view/ViewAction.java @@ -8,6 +8,7 @@ import java.lang.annotation.Target; import de.metas.process.ProcessPreconditionsResolution; +import de.metas.ui.web.process.ProcessInstanceResult.ResultAction; import de.metas.ui.web.view.IView; import de.metas.ui.web.window.datatypes.DocumentIdsSelection; import de.metas.ui.web.window.datatypes.PanelLayoutType; @@ -34,6 +35,25 @@ * #L% */ +/** + * View action annotation. + * + * Accepted arguments: + *
    + *
  • the {@link IView} + *
  • selected document ids: {@link DocumentIdsSelection} + *
  • parameter fields annotated with {@link ViewActionParam} + *
+ * + * Accepted return types: + *
    + *
  • void + *
  • {@link ResultAction} like "open included view", "open report" etc + *
+ * + * @author metas-dev + * + */ @Inherited @Documented @Retention(RetentionPolicy.RUNTIME) @@ -45,7 +65,7 @@ String description() default ""; boolean defaultAction() default false; - + PanelLayoutType layoutType() default PanelLayoutType.Panel; Class precondition() default AlwaysAllowPrecondition.class; diff --git a/src/main/java/de/metas/ui/web/process/view/ViewActionInstance.java b/src/main/java/de/metas/ui/web/process/view/ViewActionInstance.java index fe57395c4..aa00e1f70 100644 --- a/src/main/java/de/metas/ui/web/process/view/ViewActionInstance.java +++ b/src/main/java/de/metas/ui/web/process/view/ViewActionInstance.java @@ -16,8 +16,11 @@ import de.metas.ui.web.process.IProcessInstanceController; import de.metas.ui.web.process.ProcessId; import de.metas.ui.web.process.ProcessInstanceResult; +import de.metas.ui.web.process.ProcessInstanceResult.CreateAndOpenIncludedViewAction; +import de.metas.ui.web.process.ProcessInstanceResult.OpenIncludedViewAction; import de.metas.ui.web.process.ProcessInstanceResult.ResultAction; import de.metas.ui.web.view.IView; +import de.metas.ui.web.view.IViewsRepository; import de.metas.ui.web.window.datatypes.DocumentId; import de.metas.ui.web.window.datatypes.DocumentIdsSelection; import de.metas.ui.web.window.datatypes.LookupValuesList; @@ -76,8 +79,7 @@ private ViewActionInstance( @NonNull final IView view, @NonNull final ViewActionDescriptor viewActionDescriptor, @NonNull final DocumentIdsSelection selectedDocumentIds, - @NonNull final IDocumentChangesCollector changesCollector - ) + @NonNull final IDocumentChangesCollector changesCollector) { processId = ViewProcessInstancesRepository.buildProcessId(view.getViewId(), viewActionDescriptor.getActionId()); this.pinstanceId = pinstanceId; @@ -109,7 +111,7 @@ private IView getView() } return view; } - + public ProcessId getProcessId() { return processId; @@ -157,7 +159,7 @@ public ProcessInstanceResult getExecutionResult() } @Override - public ProcessInstanceResult startProcess() + public ProcessInstanceResult startProcess(final IViewsRepository viewsRepo) { assertNotExecuted(); @@ -182,9 +184,10 @@ public ProcessInstanceResult startProcess() final Object targetObject = Modifier.isStatic(viewActionMethod.getModifiers()) ? null : view; final Object resultActionObj = viewActionMethod.invoke(targetObject, viewActionParams); final ResultAction resultAction = viewActionDescriptor.convertReturnType(resultActionObj); + final ResultAction resultActionProcessed = processResultAction(resultAction, viewsRepo); final ProcessInstanceResult result = ProcessInstanceResult.builder(pinstanceId) - .setAction(resultAction) + .setAction(resultActionProcessed) .build(); this.result = result; @@ -196,6 +199,22 @@ public ProcessInstanceResult startProcess() } } + private ResultAction processResultAction(final ResultAction resultAction, final IViewsRepository viewRepos) + { + if (resultAction == null) + { + return null; + } + + if (resultAction instanceof CreateAndOpenIncludedViewAction) + { + final IView view = viewRepos.createView(((CreateAndOpenIncludedViewAction)resultAction).getCreateViewRequest()); + return OpenIncludedViewAction.builder().viewId(view.getViewId()).build(); + } + + return resultAction; + } + /* package */ void assertNotExecuted() { Check.assumeNull(result, "view action instance not already executed"); diff --git a/src/main/java/de/metas/ui/web/view/SqlViewFactory.java b/src/main/java/de/metas/ui/web/view/SqlViewFactory.java index 271f7f274..2c5bd6ec0 100644 --- a/src/main/java/de/metas/ui/web/view/SqlViewFactory.java +++ b/src/main/java/de/metas/ui/web/view/SqlViewFactory.java @@ -14,6 +14,7 @@ import de.metas.ui.web.document.filter.DocumentFilter; import de.metas.ui.web.document.filter.DocumentFilterDescriptor; import de.metas.ui.web.document.filter.DocumentFilterDescriptorsProvider; +import de.metas.ui.web.picking.PickingConstants; import de.metas.ui.web.view.descriptor.SqlViewBinding; import de.metas.ui.web.view.descriptor.SqlViewGroupingBinding; import de.metas.ui.web.view.descriptor.SqlViewRowFieldBinding; @@ -104,24 +105,25 @@ public Collection getViewFilterDescriptors(final Windo @Override public IView createView(final CreateViewRequest request) { - if (!request.getFilterOnlyIds().isEmpty()) - { - throw new IllegalArgumentException("Filtering by Ids are not supported: " + request); - } - final SqlViewBindingKey sqlViewBindingKey = new SqlViewBindingKey(request.getWindowId(), request.getViewTypeRequiredFieldCharacteristic()); final SqlViewBinding sqlViewBinding = getViewBinding(sqlViewBindingKey); final SqlViewDataRepository sqlViewDataRepository = new SqlViewDataRepository(sqlViewBinding); - return DefaultView.builder(sqlViewDataRepository) + final DefaultView.Builder viewBuilder = DefaultView.builder(sqlViewDataRepository) .setWindowId(request.getWindowId()) .setViewType(request.getViewType()) .setReferencingDocumentPaths(request.getReferencingDocumentPaths()) .setParentViewId(request.getParentViewId()) .addStickyFilters(request.getStickyFilters()) .addStickyFilter(extractReferencedDocumentFilter(request.getWindowId(), request.getSingleReferencingDocumentPathOrNull())) - .setFiltersFromJSON(request.getFilters()) - .build(); + .setFiltersFromJSON(request.getFilters()); + + if (!request.getFilterOnlyIds().isEmpty()) + { + viewBuilder.addStickyFilter(DocumentFilter.inArrayFilter(sqlViewBinding.getKeyColumnName(), sqlViewBinding.getKeyColumnName(), request.getFilterOnlyIds())); + } + + return viewBuilder.build(); } private final DocumentFilter extractReferencedDocumentFilter(final WindowId targetWindowId, final DocumentPath referencedDocumentPath) @@ -150,7 +152,7 @@ private SqlViewBinding createViewBinding(final SqlViewBindingKey key) final DocumentFilterDescriptorsProvider filterDescriptors = entityDescriptor.getFilterDescriptors(); final SqlViewGroupingBinding groupingBinding; - if (entityDescriptor.getWindowId().toIntOr(-1) == 540345) // FIXME: HARDCODED + if (PickingConstants.WINDOWID_PackageableView.equals(entityDescriptor.getWindowId())) // FIXME: HARDCODED { groupingBinding = SqlViewGroupingBinding.builder() .groupBy(I_M_Packageable_V.COLUMNNAME_M_Warehouse_ID) diff --git a/src/main/java/de/metas/ui/web/view/descriptor/ViewLayout.java b/src/main/java/de/metas/ui/web/view/descriptor/ViewLayout.java index 5a839eecb..1fc8165d7 100644 --- a/src/main/java/de/metas/ui/web/view/descriptor/ViewLayout.java +++ b/src/main/java/de/metas/ui/web/view/descriptor/ViewLayout.java @@ -21,9 +21,14 @@ import de.metas.ui.web.cache.ETag; import de.metas.ui.web.cache.ETagAware; import de.metas.ui.web.document.filter.DocumentFilterDescriptor; +import de.metas.ui.web.view.IViewRow; +import de.metas.ui.web.view.descriptor.annotation.ViewColumnHelper; import de.metas.ui.web.window.datatypes.WindowId; import de.metas.ui.web.window.descriptor.DetailId; import de.metas.ui.web.window.descriptor.DocumentLayoutElementDescriptor; +import de.metas.ui.web.window.descriptor.factory.standard.LayoutFactory; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; /* * #%L @@ -112,12 +117,13 @@ private ViewLayout(final Builder builder) /** copy and override constructor */ private ViewLayout(final ViewLayout from, + final WindowId windowId, final ImmutableList filters, final String allowNewCaption, final boolean hasTreeSupport, final boolean treeCollapsible, final int treeExpandedDepth) { super(); - windowId = from.windowId; + this.windowId = windowId; detailId = from.detailId; caption = from.caption; description = from.description; @@ -159,6 +165,11 @@ public String toString() .toString(); } + public ChangeBuilder toBuilder() + { + return new ChangeBuilder(this); + } + public WindowId getWindowId() { return windowId; @@ -197,24 +208,10 @@ public List getFilters() public ViewLayout withFiltersAndTreeSupport(final Collection filtersToSet, final boolean hasTreeSupportToSet, final Boolean treeCollapsibleToSet, final Integer treeExpandedDepthToSet) { - final ImmutableList filtersToSetEffective = filtersToSet != null ? ImmutableList.copyOf(filtersToSet) : ImmutableList.of(); - final boolean treeCollapsibleEffective = treeCollapsibleToSet != null ? treeCollapsibleToSet.booleanValue() : treeCollapsible; - final int treeExpandedDepthEffective = treeExpandedDepthToSet != null ? treeExpandedDepthToSet.intValue() : treeExpandedDepth; - - // If there will be no change then return this - if (Objects.equals(filters, filtersToSetEffective) - && hasTreeSupport == hasTreeSupportToSet - && treeCollapsible == treeCollapsibleEffective - && treeExpandedDepth == treeExpandedDepthEffective) - { - return this; - } - - // Create a copy of this layout and override what was required - return new ViewLayout(this, - filtersToSetEffective, - allowNewCaption, - hasTreeSupportToSet, treeCollapsibleEffective, treeExpandedDepthEffective); + return toBuilder() + .filters(filtersToSet) + .treeSupport(hasTreeSupportToSet, treeCollapsibleToSet, treeExpandedDepthToSet) + .build(); } public ViewLayout withAllowNewRecordIfPresent(final Optional allowNewCaption) @@ -224,16 +221,9 @@ public ViewLayout withAllowNewRecordIfPresent(final Optional allowNewCap return this; } - final String allowNewCaptionEffective = allowNewCaption.get(); - if (Objects.equals(this.allowNewCaption, allowNewCaptionEffective)) - { - return this; - } - - return new ViewLayout(this, - filters, - allowNewCaptionEffective, - hasTreeSupport, treeCollapsible, treeExpandedDepth); + return toBuilder() + .allowNewCaption(allowNewCaption.get()) + .build(); } public List getElements() @@ -295,14 +285,78 @@ public ETag getETag() // // // + // + // + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + public static final class ChangeBuilder + { + private final ViewLayout from; + private WindowId windowId; + private Collection filters; + private String allowNewCaption; + private Boolean hasTreeSupport; + private Boolean treeCollapsible; + private Integer treeExpandedDepth; + + public ViewLayout build() + { + final WindowId windowIdEffective = windowId != null ? windowId : from.windowId; + final ImmutableList filtersEffective = ImmutableList.copyOf(filters != null ? filters : from.getFilters()); + final String allowNewCaptionEffective = allowNewCaption != null ? allowNewCaption : from.allowNewCaption; + final boolean hasTreeSupportEffective = hasTreeSupport != null ? hasTreeSupport.booleanValue() : from.hasTreeSupport; + final boolean treeCollapsibleEffective = treeCollapsible != null ? treeCollapsible.booleanValue() : from.treeCollapsible; + final int treeExpandedDepthEffective = treeExpandedDepth != null ? treeExpandedDepth.intValue() : from.treeExpandedDepth; + + // If there will be no change then return this + if (Objects.equals(from.windowId, windowIdEffective) + && Objects.equals(from.filters, filtersEffective) + && Objects.equals(from.allowNewCaption, allowNewCaptionEffective) + && from.hasTreeSupport == hasTreeSupportEffective + && from.treeCollapsible == treeCollapsibleEffective + && from.treeExpandedDepth == treeExpandedDepthEffective) + { + return from; + } + + return new ViewLayout(from, windowIdEffective, filtersEffective, allowNewCaptionEffective, hasTreeSupportEffective, treeCollapsibleEffective, treeExpandedDepthEffective); + } + + public ChangeBuilder windowId(WindowId windowId) + { + this.windowId = windowId; + return this; + } + + public ChangeBuilder allowNewCaption(final String allowNewCaption) + { + this.allowNewCaption = allowNewCaption; + return this; + } + + public ChangeBuilder filters(Collection filters) + { + this.filters = filters; + return this; + } + + public ChangeBuilder treeSupport(final boolean hasTreeSupport, final Boolean treeCollapsible, final Integer treeExpandedDepth) + { + this.hasTreeSupport = hasTreeSupport; + this.treeCollapsible = treeCollapsible; + this.treeExpandedDepth = treeExpandedDepth; + return this; + } + + } + public static final class Builder { - public WindowId windowId; + private WindowId windowId; private DetailId detailId; private ITranslatableString caption; private ITranslatableString description; - private ITranslatableString emptyResultText; - private ITranslatableString emptyResultHint; + private ITranslatableString emptyResultText = LayoutFactory.HARDCODED_TAB_EMPTY_RESULT_TEXT; + private ITranslatableString emptyResultHint = LayoutFactory.HARDCODED_TAB_EMPTY_RESULT_HINT; private Collection filters = null; @@ -407,6 +461,13 @@ public Builder addElements(final Stream return this; } + public Builder addElementsFromViewRowClass(final Class viewRowClass) + { + ViewColumnHelper.getLayoutElementsForClass(viewRowClass) + .forEach(this::addElement); + return this; + } + public boolean hasElements() { return !elementBuilders.isEmpty(); diff --git a/src/main/java/de/metas/ui/web/view/descriptor/annotation/ViewColumn.java b/src/main/java/de/metas/ui/web/view/descriptor/annotation/ViewColumn.java new file mode 100644 index 000000000..5e9adc62c --- /dev/null +++ b/src/main/java/de/metas/ui/web/view/descriptor/annotation/ViewColumn.java @@ -0,0 +1,40 @@ +package de.metas.ui.web.view.descriptor.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import de.metas.ui.web.window.descriptor.DocumentFieldWidgetType; + +/* + * #%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 + * . + * #L% + */ +@Target({ ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface ViewColumn +{ + DocumentFieldWidgetType widgetType(); + + String captionKey() default ""; + + int seqNo() default Integer.MAX_VALUE; +} diff --git a/src/main/java/de/metas/ui/web/view/descriptor/annotation/ViewColumnHelper.java b/src/main/java/de/metas/ui/web/view/descriptor/annotation/ViewColumnHelper.java new file mode 100644 index 000000000..3170f4e34 --- /dev/null +++ b/src/main/java/de/metas/ui/web/view/descriptor/annotation/ViewColumnHelper.java @@ -0,0 +1,211 @@ +package de.metas.ui.web.view.descriptor.annotation; + +import java.lang.reflect.Field; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; + +import org.adempiere.exceptions.AdempiereException; +import org.adempiere.util.Services; +import org.adempiere.util.reflect.FieldReference; +import org.reflections.ReflectionUtils; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import de.metas.i18n.IMsgBL; +import de.metas.i18n.ITranslatableString; +import de.metas.printing.esb.base.util.Check; +import de.metas.ui.web.view.IViewRow; +import de.metas.ui.web.window.datatypes.Values; +import de.metas.ui.web.window.descriptor.DocumentFieldWidgetType; +import de.metas.ui.web.window.descriptor.DocumentLayoutElementDescriptor; +import de.metas.ui.web.window.descriptor.DocumentLayoutElementFieldDescriptor; +import lombok.Builder; +import lombok.NonNull; +import lombok.Singular; +import lombok.Value; +import lombok.experimental.UtilityClass; + +/* + * #%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 + * . + * #L% + */ + +@UtilityClass +public final class ViewColumnHelper +{ + private static final LoadingCache, ClassViewLayout> layoutsByClass = CacheBuilder.newBuilder() + .weakKeys() + .build(new CacheLoader, ClassViewLayout>() + { + @Override + public ClassViewLayout load(final Class dataType) throws Exception + { + return createClassViewLayout(dataType); + } + }); + + public static void cacheReset() + { + layoutsByClass.invalidateAll(); + layoutsByClass.cleanUp(); + } + + private static ClassViewLayout getClassViewLayout(@NonNull final Class dataType) + { + try + { + return layoutsByClass.get(dataType); + } + catch (final ExecutionException e) + { + throw AdempiereException.wrapIfNeeded(e).setParameter("dataType", dataType); + } + } + + public static List getLayoutElementsForClass(final Class dataType) + { + return getClassViewLayout(dataType) + .getColumns().stream() + .map(column -> createLayoutElement(column)) + .collect(ImmutableList.toImmutableList()); + } + + private static ClassViewLayout createClassViewLayout(final Class dataType) + { + @SuppressWarnings("unchecked") + final Set fields = ReflectionUtils.getAllFields(dataType, ReflectionUtils.withAnnotations(ViewColumn.class)); + + final ImmutableList columns = fields.stream() + .sorted(Comparator.comparing(field -> extractSeqNo(field))) + .map(field -> createClassViewColumnLayout(field)) + .collect(ImmutableList.toImmutableList()); + if (columns.isEmpty()) + { + return ClassViewLayout.EMPTY; + } + + return ClassViewLayout.builder() + .className(dataType.getName()) + .columns(columns) + .build(); + + } + + private static final int extractSeqNo(final Field field) + { + ViewColumn viewColumnAnn = field.getAnnotation(ViewColumn.class); + if(viewColumnAnn == null) + { + return Integer.MAX_VALUE; + } + int seqNo = viewColumnAnn.seqNo(); + return seqNo >= 0 ? seqNo : Integer.MAX_VALUE; + } + + private static ClassViewColumnLayout createClassViewColumnLayout(final Field field) + { + final ViewColumn viewColumnAnn = field.getAnnotation(ViewColumn.class); + final String fieldName = field.getName(); + final String captionKey = Check.isEmpty(viewColumnAnn.captionKey()) ? viewColumnAnn.captionKey() : fieldName; + + return ClassViewColumnLayout.builder() + .fieldName(fieldName) + .caption(Services.get(IMsgBL.class).translatable(captionKey)) + .widgetType(viewColumnAnn.widgetType()) + .fieldReference(FieldReference.of(field)) + .build(); + } + + private static DocumentLayoutElementDescriptor.Builder createLayoutElement(final ClassViewColumnLayout column) + { + return DocumentLayoutElementDescriptor.builder() + .setWidgetType(column.getWidgetType()) + .setGridElement() + .addField(DocumentLayoutElementFieldDescriptor.builder(column.getFieldName())); + } + + public static ImmutableMap extractJsonMap(final T row) + { + final Class rowClass = row.getClass(); + final Map result = new LinkedHashMap<>(); + getClassViewLayout(rowClass) + .getColumns() + .forEach(column -> { + final Object value = extractFieldValueAsJson(row, column); + if (value != null) + { + result.put(column.getFieldName(), value); + } + }); + + return ImmutableMap.copyOf(result); + } + + private static final Object extractFieldValueAsJson(final T row, final ClassViewColumnLayout column) + { + final Field field = column.getFieldReference().getField(); + if (!field.isAccessible()) + { + field.setAccessible(true); + } + try + { + final Object value = field.get(row); + return Values.valueToJsonObject(value); + } + catch (final Exception e) + { + throw AdempiereException.wrapIfNeeded(e); + } + } + + @Value + @Builder + private static final class ClassViewLayout + { + public static final ClassViewLayout EMPTY = builder().build(); + + private final String className; + @Singular + private final ImmutableList columns; + } + + @Value + @Builder + private static final class ClassViewColumnLayout + { + @NonNull + private final String fieldName; + @NonNull + private final ITranslatableString caption; + @NonNull + private final DocumentFieldWidgetType widgetType; + @NonNull + private final FieldReference fieldReference; + } +} diff --git a/src/main/java/de/metas/ui/web/window/controller/DocumentPermissionsHelper.java b/src/main/java/de/metas/ui/web/window/controller/DocumentPermissionsHelper.java index 5aa122674..665871a19 100644 --- a/src/main/java/de/metas/ui/web/window/controller/DocumentPermissionsHelper.java +++ b/src/main/java/de/metas/ui/web/window/controller/DocumentPermissionsHelper.java @@ -66,7 +66,14 @@ public static ElementPermission checkWindowAccess(@NonNull final DocumentEntityD public static void assertWindowAccess(final WindowId windowId, final String windowName, final IUserRolePermissions permissions) { - final int adWindowId = windowId.toInt(); + final int adWindowId = windowId.toIntOr(-1); + if(adWindowId < 0) + { + // cannot apply window access if the WindowId is not integer. + // usually those are special window placeholders. + return; // accept it + } + final ElementPermission windowPermission = permissions.checkWindowPermission(adWindowId); if (!windowPermission.hasReadAccess()) { diff --git a/src/main/java/de/metas/ui/web/window/datatypes/DocumentId.java b/src/main/java/de/metas/ui/web/window/datatypes/DocumentId.java index da2898e1a..a91d9709e 100644 --- a/src/main/java/de/metas/ui/web/window/datatypes/DocumentId.java +++ b/src/main/java/de/metas/ui/web/window/datatypes/DocumentId.java @@ -14,6 +14,7 @@ import com.fasterxml.jackson.annotation.JsonValue; import de.metas.printing.esb.base.util.Check; +import lombok.NonNull; /* * #%L @@ -141,6 +142,27 @@ public String toString() public abstract int toInt(); + public int removePrefixAndConvertToInt(@NonNull final String idPrefix) + { + if (isInt()) + { + return toInt(); + } + + if (idPrefix.isEmpty()) + { + return toInt(); + } + + String idStr = toJson(); + if (idStr.startsWith(idPrefix)) + { + idStr = idStr.substring(idPrefix.length()); + } + + return Integer.parseInt(idStr); + } + public abstract boolean isNew(); public int toIntOr(final int fallbackValue)