Author: lektran
Date: Tue Jul 28 12:13:28 2009
New Revision: 798500
URL: http://svn.apache.org/viewvc?rev=798500&view=rev
Log:
Implemented a services to keep track of products purchased together by creating
ProductAssoc records of type ALSO_BOUGHT.
These associated products are then shown to the customer on the product pages
as "Customers who bought this item also bought: ..." sorted by popularity.
Modified:
ofbiz/trunk/applications/order/data/OrderScheduledServices.xml
ofbiz/trunk/applications/order/servicedef/services.xml
ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderServices.java
ofbiz/trunk/applications/order/webapp/ordermgr/WEB-INF/actions/entry/catalog/ProductDetail.groovy
ofbiz/trunk/applications/product/config/ProductUiLabels.xml
ofbiz/trunk/applications/product/servicedef/services_view.xml
ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductServices.java
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/productdetail.ftl
Modified: ofbiz/trunk/applications/order/data/OrderScheduledServices.xml
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/data/OrderScheduledServices.xml?rev=798500&r1=798499&r2=798500&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/data/OrderScheduledServices.xml (original)
+++ ofbiz/trunk/applications/order/data/OrderScheduledServices.xml Tue Jul 28
12:13:28 2009
@@ -29,5 +29,6 @@
<JobSandbox jobId="8005" jobName="Extend expired Subscriptions"
runTime="2000-01-01 03:00:00.000" serviceName="runSubscriptionAutoReorders"
poolId="pool" runAsUser="system" tempExprId="MIDNIGHT_DAILY"
maxRecurrenceCount="-1"/>
<JobSandbox jobId="8006" jobName="Cancels all orders after date"
runTime="2009-12-03 03:00:00.000" serviceName="cancelAllBackOrders"
poolId="pool" runAsUser="system" tempExprId="MIDNIGHT_DAILY"
maxRecurrenceCount="-1"/>
<JobSandbox jobId="8007" jobName="Replacement Held Order Auto-Cancel"
runTime="2000-01-01 00:00:00.000" serviceName="autoCancelReplacementOrders"
poolId="pool" runAsUser="system" tempExprId="MIDNIGHT_DAILY"
maxRecurrenceCount="-1"/>
+ <JobSandbox jobId="8008" jobName="Create Also Bought Product Associations"
runTime="2000-01-01 00:00:00.000" serviceName="createAlsoBoughtProductAssocs"
poolId="pool" runAsUser="system" tempExprId="MIDNIGHT_DAILY"
maxRecurrenceCount="-1"/>
</entity-engine-xml>
Modified: ofbiz/trunk/applications/order/servicedef/services.xml
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/servicedef/services.xml?rev=798500&r1=798499&r2=798500&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/servicedef/services.xml (original)
+++ ofbiz/trunk/applications/order/servicedef/services.xml Tue Jul 28 12:13:28
2009
@@ -1008,4 +1008,25 @@
<attribute name="shipGroupSeqId" type="String" mode="IN"
optional="false"/>
<attribute name="giftMessage" type="String" mode="IN" optional="true"/>
</service>
+ <service name="createAlsoBoughtProductAssocs" engine="java" auth="true"
+ location="org.ofbiz.order.order.OrderServices"
invoke="createAlsoBoughtProductAssocs">
+ <description>
+ Cycles through all newly created sales orders and creates
ProductAssoc records (of type ALSO_BOUGHT) for products
+ that were purchased together. If a ProductAssoc record already
exists then the quantity field is incremented by one.
+ Newly created orders are determined by looking for orders that
were created after the JobSandbox.startDateTime of the
+ previous async execution of this service, alternatively the
service can be supplied with a orderEntryFromDateTime
+ parameter which will process all orders placed after that
date/time or as a final option processAllOrders can be set
+ to true to force a calculation of all orders ever placed with
orderEntryFromDateTime being ignored.
+ </description>
+ <attribute name="orderEntryFromDateTime" mode="IN" type="Timestamp"
optional="true"/>
+ <attribute name="processAllOrders" mode="IN" type="Boolean"
optional="true"/>
+ </service>
+ <service name="createAlsoBoughtProductAssocsForOrder" engine="java"
auth="true"
+ location="org.ofbiz.order.order.OrderServices"
invoke="createAlsoBoughtProductAssocsForOrder">
+ <description>
+ Creates ProductAssoc records (of type ALSO_BOUGHT) for products
that were purchased together in the Order. If a ProductAssoc record already
exists then the quantity field is incremented by one. If a variant product has
+ been ordered then the association is made to its parent product.
+ </description>
+ <attribute name="orderId" mode="IN" type="String" optional="false"/>
+ </service>
</services>
\ No newline at end of file
Modified:
ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java?rev=798500&r1=798499&r2=798500&view=diff
==============================================================================
---
ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
(original)
+++
ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderReadHelper.java
Tue Jul 28 12:13:28 2009
@@ -75,7 +75,7 @@
protected GenericValue orderHeader = null;
protected List orderItemAndShipGrp = null;
- protected List orderItems = null;
+ protected List<GenericValue> orderItems = null;
protected List adjustments = null;
protected List<GenericValue> paymentPrefs = null;
protected List orderStatuses = null;
@@ -1366,7 +1366,7 @@
// ========== Order Item Methods ==========
// ========================================
- public List getOrderItems() {
+ public List<GenericValue> getOrderItems() {
if (orderItems == null) {
try {
orderItems = orderHeader.getRelated("OrderItem",
UtilMisc.toList("orderItemSeqId"));
Modified:
ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderServices.java
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderServices.java?rev=798500&r1=798499&r2=798500&view=diff
==============================================================================
--- ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderServices.java
(original)
+++ ofbiz/trunk/applications/order/src/org/ofbiz/order/order/OrderServices.java
Tue Jul 28 12:13:28 2009
@@ -21,7 +21,6 @@
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.ArrayList;
-import com.ibm.icu.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
@@ -31,6 +30,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
import javax.transaction.Transaction;
@@ -56,10 +56,10 @@
import org.ofbiz.entity.condition.EntityCondition;
import org.ofbiz.entity.condition.EntityConditionList;
import org.ofbiz.entity.condition.EntityExpr;
-import org.ofbiz.entity.condition.EntityJoinOperator;
import org.ofbiz.entity.condition.EntityOperator;
import org.ofbiz.entity.transaction.GenericTransactionException;
import org.ofbiz.entity.transaction.TransactionUtil;
+import org.ofbiz.entity.util.EntityFindOptions;
import org.ofbiz.entity.util.EntityListIterator;
import org.ofbiz.entity.util.EntityUtil;
import org.ofbiz.order.shoppingcart.CartItemModifyException;
@@ -81,6 +81,8 @@
import org.ofbiz.service.ModelService;
import org.ofbiz.service.ServiceUtil;
+import com.ibm.icu.util.Calendar;
+
/**
* Order Processing Services
*/
@@ -91,8 +93,8 @@
public static final String resource = "OrderUiLabels";
public static final String resource_error = "OrderErrorUiLabels";
- public static Map salesAttributeRoleMap = FastMap.newInstance();
- public static Map purchaseAttributeRoleMap = FastMap.newInstance();
+ public static Map<String, String> salesAttributeRoleMap =
FastMap.newInstance();
+ public static Map<String, String> purchaseAttributeRoleMap =
FastMap.newInstance();
static {
salesAttributeRoleMap.put("placingCustomerPartyId",
"PLACING_CUSTOMER");
salesAttributeRoleMap.put("billToCustomerPartyId", "BILL_TO_CUSTOMER");
@@ -145,7 +147,7 @@
hasPermission = true;
} else {
// check sales agent/customer relationship
- List repsCustomers = new LinkedList();
+ List<GenericValue> repsCustomers = new
LinkedList<GenericValue>();
try {
repsCustomers =
EntityUtil.filterByDate(userLogin.getRelatedOne("Party").getRelatedByAnd("FromPartyRelationship",
UtilMisc.toMap("roleTypeIdFrom", "AGENT",
"roleTypeIdTo", "CUSTOMER", "partyIdTo", partyId)));
@@ -5300,4 +5302,145 @@
}
return ServiceUtil.returnSuccess();
}
+
+ public static Map<String, Object>
createAlsoBoughtProductAssocs(DispatchContext dctx, Map context) {
+ GenericDelegator delegator = dctx.getDelegator();
+ LocalDispatcher dispatcher = dctx.getDispatcher();
+ // All orders with an entryDate > orderEntryFromDateTime will be
processed
+ Timestamp orderEntryFromDateTime = (Timestamp)
context.get("orderEntryFromDateTime");
+ // If true all orders ever created will be processed and any
pre-existing ALSO_BOUGHT ProductAssocs will be expired
+ boolean processAllOrders = context.get("processAllOrders") == null ?
false : (Boolean) context.get("processAllOrders");
+ if (orderEntryFromDateTime == null && !processAllOrders) {
+ // No from date supplied, check to see when this service last ran
and use the startDateTime
+ EntityCondition cond =
EntityCondition.makeCondition(UtilMisc.toMap("statusId", "SERVICE_FINISHED",
"serviceName", "createAlsoBoughtProductAssocs"));
+ EntityFindOptions efo = new EntityFindOptions();
+ efo.setMaxRows(1);
+ try {
+ GenericValue lastRunJobSandbox =
EntityUtil.getFirst(delegator.findList("JobSandbox", cond, null,
UtilMisc.toList("startDateTime DESC"), efo, false));
+ if (lastRunJobSandbox != null) {
+ orderEntryFromDateTime =
lastRunJobSandbox.getTimestamp("startDateTime");
+ }
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ }
+ if (orderEntryFromDateTime == null) {
+ // Still null, process all orders
+ processAllOrders = true;
+ }
+ }
+ if (processAllOrders) {
+ // Expire any pre-existing ALSO_BOUGHT ProductAssocs in
preparation for reprocessing
+ EntityCondition cond =
EntityCondition.makeCondition(UtilMisc.toList(
+ EntityCondition.makeCondition("productAssocTypeId",
"ALSO_BOUGHT"),
+ EntityCondition.makeConditionDate("fromDate", "thruDate")
+ ));
+ try {
+ delegator.storeByCondition("ProductAssoc",
UtilMisc.toMap("thruDate", UtilDateTime.nowTimestamp()), cond);
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ }
+ }
+ EntityListIterator eli = null;
+ try {
+ List<EntityExpr> orderCondList =
UtilMisc.toList(EntityCondition.makeCondition("orderTypeId", "SALES_ORDER"));
+ if (!processAllOrders && orderEntryFromDateTime != null) {
+ orderCondList.add(EntityCondition.makeCondition("entryDate",
EntityOperator.GREATER_THAN, orderEntryFromDateTime));
+ }
+ EntityCondition cond =
EntityCondition.makeCondition(orderCondList);
+ eli = delegator.find("OrderHeader", cond, null, null,
UtilMisc.toList("entryDate ASC"), null);
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ return ServiceUtil.returnError(e.getMessage());
+ }
+ if (eli != null) {
+ GenericValue orderHeader = null;
+ while ((orderHeader = eli.next()) != null) {
+ Map svcIn = FastMap.newInstance();
+ svcIn.put("userLogin", context.get("userLogin"));
+ svcIn.put("orderId", orderHeader.get("orderId"));
+ try {
+
dispatcher.runSync("createAlsoBoughtProductAssocsForOrder", svcIn);
+ } catch (GenericServiceException e) {
+ Debug.logError(e, module);
+ }
+ }
+ try {
+ eli.close();
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ }
+ }
+ return ServiceUtil.returnSuccess();
+ }
+
+ public static Map<String, Object>
createAlsoBoughtProductAssocsForOrder(DispatchContext dctx, Map context) {
+ LocalDispatcher dispatcher = dctx.getDispatcher();
+ GenericDelegator delegator = dctx.getDelegator();
+ String orderId = (String) context.get("orderId");
+ OrderReadHelper orh = new OrderReadHelper(delegator, orderId);
+ List<GenericValue> orderItems = orh.getOrderItems();
+ // In order to improve efficiency a little bit, we will always create
the ProductAssoc records
+ // with productId < productIdTo when the two are compared. This way
when checking for an existing
+ // record we don't have to check both possible combinations of
productIds
+ TreeSet<String> productIdSet = new TreeSet<String>();
+ if (orderItems != null) {
+ for (GenericValue orderItem : orderItems) {
+ String productId = orderItem.getString("productId");
+ if (productId != null) {
+ GenericValue parentProduct =
ProductWorker.getParentProduct(productId, delegator);
+ if (parentProduct != null) productId =
parentProduct.getString("productId");
+ productIdSet.add(productId);
+ }
+ }
+ }
+ TreeSet<String> productIdToSet = new TreeSet<String>(productIdSet);
+ for (String productId : productIdSet) {
+ productIdToSet.remove(productId);
+ for (String productIdTo : productIdToSet) {
+ EntityCondition cond = EntityCondition.makeCondition(
+ UtilMisc.toList(
+ EntityCondition.makeCondition("productId",
productId),
+ EntityCondition.makeCondition("productIdTo",
productIdTo),
+
EntityCondition.makeCondition("productAssocTypeId", "ALSO_BOUGHT"),
+ EntityCondition.makeCondition("fromDate",
EntityOperator.LESS_THAN_EQUAL_TO, UtilDateTime.nowTimestamp()),
+ EntityCondition.makeCondition("thruDate", null)
+ )
+ );
+ GenericValue existingProductAssoc = null;
+ try {
+ // No point in using the cache because of the
filterByDateExpr
+ existingProductAssoc =
EntityUtil.getFirst(delegator.findList("ProductAssoc", cond, null,
UtilMisc.toList("fromDate DESC"), null, false));
+ } catch (GenericEntityException e) {
+ Debug.logError(e, module);
+ }
+ try {
+ if (existingProductAssoc != null) {
+ BigDecimal newQuantity =
existingProductAssoc.getBigDecimal("quantity");
+ if (newQuantity == null ||
newQuantity.compareTo(BigDecimal.ZERO) < 0) {
+ newQuantity = BigDecimal.ZERO;
+ }
+ newQuantity = newQuantity.add(BigDecimal.ONE);
+ ModelService updateProductAssoc =
dctx.getModelService("updateProductAssoc");
+ Map<String, Object> updateCtx =
updateProductAssoc.makeValid(context, ModelService.IN_PARAM, true, null);
+
updateCtx.putAll(updateProductAssoc.makeValid(existingProductAssoc,
ModelService.IN_PARAM));
+ updateCtx.put("quantity", newQuantity);
+ dispatcher.runSync("updateProductAssoc", updateCtx);
+ } else {
+ Map<String, Object> createCtx = FastMap.newInstance();
+ createCtx.put("userLogin", context.get("userLogin"));
+ createCtx.put("productId", productId);
+ createCtx.put("productIdTo", productIdTo);
+ createCtx.put("productAssocTypeId", "ALSO_BOUGHT");
+ createCtx.put("fromDate", UtilDateTime.nowTimestamp());
+ createCtx.put("quantity", BigDecimal.ONE);
+ dispatcher.runSync("createProductAssoc", createCtx);
+ }
+ } catch (GenericServiceException e) {
+ Debug.logError(e, module);
+ }
+ }
+ }
+
+ return ServiceUtil.returnSuccess();
+ }
}
Modified:
ofbiz/trunk/applications/order/webapp/ordermgr/WEB-INF/actions/entry/catalog/ProductDetail.groovy
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/webapp/ordermgr/WEB-INF/actions/entry/catalog/ProductDetail.groovy?rev=798500&r1=798499&r2=798500&view=diff
==============================================================================
---
ofbiz/trunk/applications/order/webapp/ordermgr/WEB-INF/actions/entry/catalog/ProductDetail.groovy
(original)
+++
ofbiz/trunk/applications/order/webapp/ordermgr/WEB-INF/actions/entry/catalog/ProductDetail.groovy
Tue Jul 28 12:13:28 2009
@@ -379,6 +379,9 @@
}
// get product associations
+ alsoBoughtProducts = dispatcher.runSync("getAssociatedProducts",
[productId : productId, type : "ALSO_BOUGHT", checkViewAllow : true,
prodCatalogId : currentCatalogId, bidirectional : true, sortDescending : true]);
+ context.alsoBoughtProducts = alsoBoughtProducts.assocProducts;
+
obsoleteProducts = dispatcher.runSync("getAssociatedProducts", [productId
: productId, type : "PRODUCT_OBSOLESCENCE", checkViewAllow : true,
prodCatalogId : currentCatalogId]);
context.obsoleteProducts = obsoleteProducts.assocProducts;
Modified: ofbiz/trunk/applications/product/config/ProductUiLabels.xml
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/config/ProductUiLabels.xml?rev=798500&r1=798499&r2=798500&view=diff
==============================================================================
--- ofbiz/trunk/applications/product/config/ProductUiLabels.xml (original)
+++ ofbiz/trunk/applications/product/config/ProductUiLabels.xml Tue Jul 28
12:13:28 2009
@@ -7061,6 +7061,9 @@
<value xml:lang="th">Allow USPS Addr (PO Box, RR, etc)</value>
<value xml:lang="zh">å
许USPSå°å (é®ç®±ã éè´§çç)</value>
</property>
+ <property key="ProductAlsoBought">
+ <value xml:lang="en">Customers who bought this item also
bought:</value>
+ </property>
<property key="ProductAlternate">
<value xml:lang="de">Alternative</value>
<value xml:lang="en">Alternate</value>
Modified: ofbiz/trunk/applications/product/servicedef/services_view.xml
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/servicedef/services_view.xml?rev=798500&r1=798499&r2=798500&view=diff
==============================================================================
--- ofbiz/trunk/applications/product/servicedef/services_view.xml (original)
+++ ofbiz/trunk/applications/product/servicedef/services_view.xml Tue Jul 28
12:13:28 2009
@@ -63,12 +63,19 @@
</service>
<service name="getAssociatedProducts" engine="java"
location="org.ofbiz.product.product.ProductServices"
invoke="prodFindAssociatedByType">
- <description>Finds associated products by the defined
type.</description>
+ <description>
+ Finds associated products by the defined type. Only one of either
productId or productIdTo can be supplied,
+ not both. If bidirectional is set to true then the passed in
productId will be treated as both a productId
+ and a productIdTo (defaults to false). If sortDescending is true
then assocProducts will be returned sorted
+ by sequenceNum descending (defaults to false).
+ </description>
<attribute name="productId" type="String" mode="IN" optional="true"/>
<attribute name="productIdTo" type="String" mode="IN" optional="true"/>
<attribute name="checkViewAllow" type="Boolean" mode="IN"
optional="true"/>
<attribute name="prodCatalogId" type="String" mode="IN"
optional="true"/>
<attribute name="type" type="String" mode="IN"/>
+ <attribute name="bidirectional" type="Boolean" mode="IN"
optional="true"/>
+ <attribute name="sortDescending" type="Boolean" mode="IN"
optional="true"/>
<attribute name="assocProducts" type="java.util.Collection" mode="OUT"
optional="true"/>
</service>
<service name="getProductFeatures" engine="java"
Modified:
ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductServices.java
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductServices.java?rev=798500&r1=798499&r2=798500&view=diff
==============================================================================
---
ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductServices.java
(original)
+++
ofbiz/trunk/applications/product/src/org/ofbiz/product/product/ProductServices.java
Tue Jul 28 12:13:28 2009
@@ -43,6 +43,8 @@
import org.ofbiz.entity.GenericDelegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
+import org.ofbiz.entity.condition.EntityCondition;
+import org.ofbiz.entity.condition.EntityJoinOperator;
import org.ofbiz.entity.util.EntityUtil;
import org.ofbiz.product.image.ScaleImage;
import org.ofbiz.product.catalog.CatalogWorker;
@@ -423,8 +425,12 @@
String errMsg = null;
Boolean cvaBool = (Boolean) context.get("checkViewAllow");
- boolean checkViewAllow = (cvaBool == null ? false :
cvaBool.booleanValue());
+ boolean checkViewAllow = (cvaBool == null ? false : cvaBool);
String prodCatalogId = (String) context.get("prodCatalogId");
+ Boolean bidirectional = (Boolean) context.get("bidirectional");
+ bidirectional = bidirectional == null ? false : bidirectional;
+ Boolean sortDescending = (Boolean) context.get("sortDescending");
+ sortDescending = sortDescending == null ? false : sortDescending;
if (productId == null && productIdTo == null) {
errMsg =
UtilProperties.getMessage(resource,"productservices.both_productId_and_productIdTo_cannot_be_null",
locale);
@@ -462,11 +468,28 @@
try {
List<GenericValue> productAssocs = null;
+
+ List<String> orderBy = FastList.newInstance();
+ if (sortDescending) {
+ orderBy.add("sequenceNum DESC");
+ } else {
+ orderBy.add("sequenceNum");
+ }
- if (productIdTo == null) {
- productAssocs = product.getRelatedCache("MainProductAssoc",
UtilMisc.toMap("productAssocTypeId", type), UtilMisc.toList("sequenceNum"));
+ if (bidirectional) {
+ EntityCondition cond = EntityCondition.makeCondition(
+ UtilMisc.toList(
+ EntityCondition.makeCondition("productId",
productId),
+ EntityCondition.makeCondition("productIdTo",
productId)
+ ), EntityJoinOperator.OR);
+ cond = EntityCondition.makeCondition(cond,
EntityCondition.makeCondition("productAssocTypeId", type));
+ productAssocs = delegator.findList("ProductAssoc", cond, null,
orderBy, null, true);
} else {
- productAssocs = product.getRelatedCache("AssocProductAssoc",
UtilMisc.toMap("productAssocTypeId", type), UtilMisc.toList("sequenceNum"));
+ if (productIdTo == null) {
+ productAssocs =
product.getRelatedCache("MainProductAssoc",
UtilMisc.toMap("productAssocTypeId", type), orderBy);
+ } else {
+ productAssocs =
product.getRelatedCache("AssocProductAssoc",
UtilMisc.toMap("productAssocTypeId", type), orderBy);
+ }
}
// filter the list by date
productAssocs = EntityUtil.filterByDate(productAssocs);
Modified:
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/productdetail.ftl
URL:
http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/productdetail.ftl?rev=798500&r1=798499&r2=798500&view=diff
==============================================================================
---
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/productdetail.ftl
(original)
+++
ofbiz/trunk/specialpurpose/ecommerce/webapp/ecommerce/catalog/productdetail.ftl
Tue Jul 28 12:13:28 2009
@@ -696,13 +696,20 @@
<div class="productsummary-container">
<#list assocProducts as productAssoc>
+ <#if productAssoc.productId == product.productId>
+ <#assign assocProductId = productAssoc.productIdTo/>
+ <#else/>
+ <#assign assocProductId = productAssoc.productId/>
+ </#if>
<div>
- <a href="<@ofbizUrl>${targetRequest}/<#if
categoryId?exists>~category_id=${categoryId}/</#if>~product_id=${productAssoc.productIdTo?if_exists}</@ofbizUrl>"
class="buttontext">
- ${productAssoc.productIdTo?if_exists}
+ <a href="<@ofbizUrl>${targetRequest}/<#if
categoryId?exists>~category_id=${categoryId}/</#if>~product_id=${assocProductId}</@ofbizUrl>"
class="buttontext">
+ ${assocProductId}
</a>
- - <b>${productAssoc.reason?if_exists}</b>
+ <#if productAssoc.reason?has_content>
+ - <b>${productAssoc.reason}</b>
+ </#if>
</div>
- ${setRequestAttribute("optProductId", productAssoc.productIdTo)}
+ ${setRequestAttribute("optProductId", assocProductId)}
${setRequestAttribute("listIndex", listIndex)}
${setRequestAttribute("formNamePrefix", formNamePrefix)}
<#if targetRequestName?has_content>
@@ -723,6 +730,8 @@
<#assign listIndex = 1>
${setRequestAttribute("productValue", productValue)}
<div id="associated-products">
+ <#-- also bought -->
+ <@associated assocProducts=alsoBoughtProducts beforeName="" showName="N"
afterName="${uiLabelMap.ProductAlsoBought}" formNamePrefix="albt"
targetRequestName=""/>
<#-- obsolete -->
<@associated assocProducts=obsoleteProducts beforeName="" showName="Y"
afterName=" ${uiLabelMap.ProductObsolete}" formNamePrefix="obs"
targetRequestName=""/>
<#-- cross sell -->