Author: jleroux
Date: Sat May 24 04:37:16 2014 New Revision: 1597235 URL: http://svn.apache.org/r1597235 Log: A (slightly modified) patch from Yvan Cauchy for "Trigger for service to run upon subscription expiry" https://issues.apache.org/jira/browse/OFBIZ-5333 We are also proposing to add the following fields: ProductSubscriptionResource.gracePeriodOnExpiry ProductSubscriptionResource.gracePeriodOnExpiryUomId Subscription.gracePeriodOnExpiry Subscription.gracePeriodOnExpiryUomId This will allow the runServiceOnSubscriptionExpiry to trigger the service in SubscriptionResource.serviceNameOnExpiry only after nowTimeStamp >= Subscription.thruDate + Subscription.gracePeriodOnExpiry. This provides flexibility in managing the triggering of the deprovisioning service, similar to the flexibility in automatic subscription extensions enabled by Subscription.canclAutmExtTime for runSubscriptionAutoReorders. jleroux: I replaced tabs by spaces Will commit the change in serviceResult.ftl apart Slightly reformatted SubscriptionServices.runServiceOnSubscriptionExpiry() Slightly reformatted and added comment in services_subscription.xml for the 2 new services Modified: ofbiz/trunk/applications/order/data/OrderScheduledServices.xml ofbiz/trunk/applications/product/config/ProductUiLabels.xml ofbiz/trunk/applications/product/entitydef/entitymodel.xml ofbiz/trunk/applications/product/servicedef/services_subscription.xml ofbiz/trunk/applications/product/src/org/ofbiz/product/subscription/SubscriptionServices.java ofbiz/trunk/applications/product/widget/catalog/SubscriptionForms.xml ofbiz/trunk/specialpurpose/ecommerce/data/DemoProduct.xml Modified: ofbiz/trunk/applications/order/data/OrderScheduledServices.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/order/data/OrderScheduledServices.xml?rev=1597235&r1=1597234&r2=1597235&view=diff ============================================================================== --- ofbiz/trunk/applications/order/data/OrderScheduledServices.xml (original) +++ ofbiz/trunk/applications/order/data/OrderScheduledServices.xml Sat May 24 04:37:16 2014 @@ -31,5 +31,5 @@ under the License. <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"/> <JobSandbox jobId="8009" jobName="Delete auto-save shopping list for anonymous users" runTime="2000-01-01 00:00:00.000" serviceName="autoDeleteAutoSaveShoppingList" poolId="pool" runAsUser="system" tempExprId="MIDNIGHT_DAILY" maxRecurrenceCount="-1"/> - + <JobSandbox jobId="8010" jobName="Run Nominated Service on Subscription Expiry" runTime="2014-04-21 00:00:00.000" serviceName="runServiceOnSubscriptionExpiry" poolId="pool" runAsUser="system" tempExprId="MIDNIGHT_DAILY" maxRecurrenceCount="-1"/> </entity-engine-xml> Modified: ofbiz/trunk/applications/product/config/ProductUiLabels.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/config/ProductUiLabels.xml?rev=1597235&r1=1597234&r2=1597235&view=diff ============================================================================== --- ofbiz/trunk/applications/product/config/ProductUiLabels.xml (original) +++ ofbiz/trunk/applications/product/config/ProductUiLabels.xml Sat May 24 04:37:16 2014 @@ -17566,6 +17566,9 @@ <value xml:lang="zh">ååæ¶æ®</value> <value xml:lang="zh_TW">æåæ¶æ</value> </property> + <property key="ProductGracePeriodUomId"> + <value xml:lang="en">Grace Period UOM Id</value> + </property> <property key="ProductGrams"> <value xml:lang="de">Gramm</value> <value xml:lang="en">Grams</value> @@ -28795,7 +28798,7 @@ <value xml:lang="ja">ãªã½ã¼ã¹ID ${subscriptionResourceId} ã®å¦çã§ç³è¾¼ä½æä¸ã«ã¨ã©ã¼</value> <value xml:lang="vi">Lá»i tạo Bảo lãnh bán hà ng khi xá» lý Tà i nguyên có Id ${subscriptionResourceId}</value> <value xml:lang="zh">å建订é ãå½å¤çèµæºæ è¯ ${subscriptionResourceId} æ¶åºé</value> - </property> + </property> <property key="ProductSubscriptionUpdateError"> <value xml:lang="en">Error processing subscription update with ID ${subscriptionId}</value> <value xml:lang="it">Errore durante l'aggiornamento dell'abbonamento ${subscriptionId}</value> Modified: ofbiz/trunk/applications/product/entitydef/entitymodel.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/entitydef/entitymodel.xml?rev=1597235&r1=1597234&r2=1597235&view=diff ============================================================================== --- ofbiz/trunk/applications/product/entitydef/entitymodel.xml (original) +++ ofbiz/trunk/applications/product/entitydef/entitymodel.xml Sat May 24 04:37:16 2014 @@ -4394,6 +4394,8 @@ under the License. <field name="automaticExtend" type="indicator"><description>If this subscription is automatically extended with the same period as the initial period.</description></field> <field name="canclAutmExtTime" type="numeric"><description>The time period (before the end of the thruDate) after which the automatic extension of the subscription will be executed.</description></field> <field name="canclAutmExtTimeUomId" type="id"><description>Unit Of Measure used for the automatic extension of the subscription.</description></field> + <field name="gracePeriodOnExpiry" type="numeric"><description>The time period (after the end of the thruDate) after which the subscription will be expired.</description></field> + <field name="gracePeriodOnExpiryUomId" type="id"><description>Unit Of Measure used for the grace period of the subscription.</description></field> <prim-key field="productId"/> <prim-key field="subscriptionResourceId"/> <prim-key field="fromDate"/> @@ -4418,6 +4420,9 @@ under the License. <relation type="one" fk-name="PROD_SBRS_MTU" title="MaxLifeTime" rel-entity-name="Uom"> <key-map field-name="maxLifeTimeUomId" rel-field-name="uomId"/> </relation> + <relation type="one" fk-name="PROD_SBRS_GTU" title="GracePeriod" rel-entity-name="Uom"> + <key-map field-name="gracePeriodOnExpiryUomId" rel-field-name="uomId"/> + </relation> </entity> <entity entity-name="Subscription" package-name="org.ofbiz.product.subscription" @@ -4455,6 +4460,9 @@ under the License. <field name="automaticExtend" type="indicator"><description>If this subscription is automatically extended with the same period as the initial period.</description></field> <field name="canclAutmExtTime" type="numeric"><description>The time period (before the end of the thruDate) after which the automatic extension of the subscription will be executed.</description></field> <field name="canclAutmExtTimeUomId" type="id"><description>Unit Of Measure used for the automatic extension of the subscription.</description></field> + <field name="gracePeriodOnExpiry" type="numeric"><description>The time period (before the end of the thruDate) after which the automatic extension of the subscription will be executed.</description></field> + <field name="gracePeriodOnExpiryUomId" type="id"><description>Unit Of Measure used for the automatic extension of the subscription.</description></field> + <field name="serviceNameOnExpiry" type="long-varchar"><description>Name of service which will run on subscription expiration.</description></field> <prim-key field="subscriptionId"/> <relation type="one" fk-name="SUBSC_SRESRC" rel-entity-name="SubscriptionResource"> <key-map field-name="subscriptionResourceId"/> @@ -4526,6 +4534,9 @@ under the License. <relation type="many" rel-entity-name="SubscriptionTypeAttr"> <key-map field-name="subscriptionTypeId"/> </relation> + <relation type="one" fk-name="SUBSC_GTU" title="GracePeriod" rel-entity-name="Uom"> + <key-map field-name="gracePeriodOnExpiryUomId" rel-field-name="uomId"/> + </relation> </entity> <entity entity-name="SubscriptionActivity" package-name="org.ofbiz.product.subscription" title="Subscription Activity Entity"> <field name="subscriptionActivityId" type="id-ne"></field> @@ -4568,6 +4579,7 @@ under the License. <field name="description" type="description"></field> <field name="contentId" type="id"><description>Optional (use if applicable) ID of a Content record that this would represent a subscription to.</description></field> <field name="webSiteId" type="id"><description>Optional (use if applicable) ID of a WebSite record that this would represent a subscription to.</description></field> + <field name="serviceNameOnExpiry" type="long-varchar"><description>Name of service which will run on subscription expiration.</description></field> <prim-key field="subscriptionResourceId"/> <relation type="one" fk-name="SUBSC_RES_PARENT" title="Parent" rel-entity-name="SubscriptionResource"> <key-map field-name="parentResourceId" rel-field-name="subscriptionResourceId"/> Modified: ofbiz/trunk/applications/product/servicedef/services_subscription.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/servicedef/services_subscription.xml?rev=1597235&r1=1597234&r2=1597235&view=diff ============================================================================== --- ofbiz/trunk/applications/product/servicedef/services_subscription.xml (original) +++ ofbiz/trunk/applications/product/servicedef/services_subscription.xml Sat May 24 04:37:16 2014 @@ -110,6 +110,8 @@ under the License. <attribute name="canclAutmExtTime" type="Integer" mode="IN" optional="true"/> <attribute name="canclAutmExtTimeUomId" type="String" mode="IN" optional="true"/> <attribute name="alwaysCreateNewRecord" type="String" mode="IN" optional="true"><!-- This defaults to Y (true) which means new Subscription records will be created instead of updating old ones with new thruDates. This keeps a more complete history of subscription activity. --></attribute> + <attribute name="gracePeriodOnExpiry" type="Integer" mode="IN" optional="true"/> + <attribute name="gracePeriodOnExpiryUomId" type="String" mode="IN" optional="true"/> <attribute name="subscriptionId" type="String" mode="OUT" optional="false"/> </service> <service name="processExtendSubscriptionByProduct" engine="java" auth="true" @@ -155,4 +157,17 @@ under the License. <description>Subscription Permission Checking Logic</description> <implements service="permissionInterface"/> </service> + + <service name="runServiceOnSubscriptionExpiry" engine="java" auth="true" use-transaction="true" + location="org.ofbiz.product.subscription.SubscriptionServices" invoke="runServiceOnSubscriptionExpiry"> + <description>A service designed to be automatically run by job scheduler to trigger another service to run for each subscription which has expired. + This is done by looking for all subscriptions for which thruDate and gracePeriodOnExpiry are expired and where the automaticExtend flag is set to "N". + The service to run is found in SubscriptionResource.ServiceNameOnExpiry (by default OOTB: runSubscriptionExpired, see below)</description> + </service> + <service name="runSubscriptionExpired" engine="java" auth="true" use-transaction="true" + location="org.ofbiz.product.subscription.SubscriptionServices" invoke="runSubscriptionExpired"> + <description>A dummy service to test subscription expiration, expected to change depending upon the specific service logic that providers will write. + See https://issues.apache.org/jira/browse/OFBIZ-5333 for more information</description> + <attribute name="subscriptionId" type="String" mode="IN" optional="false"/> + </service> </services> Modified: ofbiz/trunk/applications/product/src/org/ofbiz/product/subscription/SubscriptionServices.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/src/org/ofbiz/product/subscription/SubscriptionServices.java?rev=1597235&r1=1597234&r2=1597235&view=diff ============================================================================== --- ofbiz/trunk/applications/product/src/org/ofbiz/product/subscription/SubscriptionServices.java (original) +++ ofbiz/trunk/applications/product/src/org/ofbiz/product/subscription/SubscriptionServices.java Sat May 24 04:37:16 2014 @@ -20,6 +20,7 @@ package org.ofbiz.product.subscription; import java.math.BigDecimal; import java.sql.Timestamp; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -35,6 +36,8 @@ import org.ofbiz.common.uom.UomWorker; import org.ofbiz.entity.Delegator; import org.ofbiz.entity.GenericEntityException; import org.ofbiz.entity.GenericValue; +import org.ofbiz.entity.condition.EntityCondition; +import org.ofbiz.entity.condition.EntityOperator; import org.ofbiz.entity.util.EntityUtil; import org.ofbiz.service.DispatchContext; import org.ofbiz.service.GenericServiceException; @@ -219,6 +222,8 @@ public class SubscriptionServices { subContext.put("automaticExtend", productSubscriptionResource.get("automaticExtend")); subContext.put("canclAutmExtTime", productSubscriptionResource.get("canclAutmExtTime")); subContext.put("canclAutmExtTimeUomId", productSubscriptionResource.get("canclAutmExtTimeUomId")); + subContext.put("gracePeriodOnExpiry", productSubscriptionResource.get("gracePeriodOnExpiry")); + subContext.put("gracePeriodOnExpiryUomId", productSubscriptionResource.get("gracePeriodOnExpiryUomId")); Map<String, Object> ctx = dctx.getModelService("processExtendSubscription").makeValid(subContext, ModelService.IN_PARAM); Map<String, Object> processExtendSubscriptionResult = dispatcher.runSync("processExtendSubscription", ctx); @@ -298,4 +303,97 @@ public class SubscriptionServices { return ServiceUtil.returnSuccess(); } + + public static Map<String, Object> runServiceOnSubscriptionExpiry( DispatchContext dctx, Map<String, ? extends Object> context) { + LocalDispatcher dispatcher = dctx.getDispatcher(); + Delegator delegator = dctx.getDelegator(); + + GenericValue userLogin = (GenericValue) context.get("userLogin"); + Map<String, Object> result = new HashMap<String, Object>(); + Map<String, Object> expiryMap = new HashMap<String, Object>(); + String gracePeriodOnExpiry = null; + String gracePeriodOnExpiryUomId = null; + String subscriptionId = null; + + try { + EntityCondition cond1 = EntityCondition.makeCondition("automaticExtend", EntityOperator.EQUALS, "N"); + EntityCondition cond2 = EntityCondition.makeCondition("automaticExtend", EntityOperator.EQUALS, null); + EntityCondition cond = EntityCondition.makeCondition(UtilMisc.toList(cond1, cond2), EntityOperator.OR); + List<GenericValue> subscriptionList = null; + subscriptionList = delegator.findList("Subscription", cond, null,null, null, false); + + if (subscriptionList != null) { + for (GenericValue subscription : subscriptionList) { + Calendar currentDate = Calendar.getInstance(); + currentDate.setTime(UtilDateTime.nowTimestamp()); + // check if the thruDate + grace period (if provided) is earlier than today's date + Calendar endDateSubscription = Calendar.getInstance(); + int field = Calendar.MONTH; + String subscriptionResourceId = subscription.getString("subscriptionResourceId"); + GenericValue subscriptionResource = null; + subscriptionResource = delegator.findOne("SubscriptionResource", UtilMisc.toMap("subscriptionResourceId", subscriptionResourceId), false); + subscriptionId = subscription.getString("subscriptionId"); + gracePeriodOnExpiry = subscription.getString("gracePeriodOnExpiry"); + gracePeriodOnExpiryUomId = subscription.getString("gracePeriodOnExpiryUomId"); + String serviceNameOnExpiry = subscriptionResource.getString("serviceNameOnExpiry"); + endDateSubscription.setTime(subscription.getTimestamp("thruDate")); + + if (gracePeriodOnExpiry != null && gracePeriodOnExpiryUomId != null) { + if ("TF_day".equals(gracePeriodOnExpiryUomId)) { + field = Calendar.DAY_OF_YEAR; + } else if ("TF_wk".equals(gracePeriodOnExpiryUomId)) { + field = Calendar.WEEK_OF_YEAR; + } else if ("TF_mon".equals(gracePeriodOnExpiryUomId)) { + field = Calendar.MONTH; + } else if ("TF_yr".equals(gracePeriodOnExpiryUomId)) { + field = Calendar.YEAR; + } else { + Debug.logWarning("Don't know anything about gracePeriodOnExpiryUomId [" + gracePeriodOnExpiryUomId + "], defaulting to month", module); + } + endDateSubscription.add(field, Integer.valueOf(gracePeriodOnExpiry).intValue()); + } + + if ((currentDate.after(endDateSubscription) || currentDate.equals(endDateSubscription)) && serviceNameOnExpiry != null) { + if (userLogin != null) { + expiryMap.put("userLogin", userLogin); + } + if (subscriptionId != null) { + expiryMap.put("subscriptionId", subscriptionId); + } + result = dispatcher.runSync(serviceNameOnExpiry, expiryMap); + if (ServiceUtil.isSuccess(result)) { + Debug.logInfo("Subscription expired successfully for subscription ID:" + subscriptionId, module); + } else if (ServiceUtil.isError(result)) { + result = null; + Debug.logError("Error expiring subscription while processing with subscriptionId: " + subscriptionId, module); + } + + if (result != null && subscriptionId != null) { + Debug.logInfo("Service mentioned in serviceNameOnExpiry called with result: " + result.get("successMessage"), module); + } else if (result == null && subscriptionId != null) { + Debug.logError("Subscription couldn't be expired for subscriptionId: " + subscriptionId, module); + return ServiceUtil.returnError("Subscription couldn't be expired for subscriptionId: " + subscriptionId); + } + } + } + } + } catch (GenericServiceException e) { + Debug.logError("Error while calling service specified in serviceNameOnExpiry", module); + return ServiceUtil.returnError(e.toString()); + } catch (GenericEntityException e) { + Debug.logError(e, module); + } + + return result; + } + + public static Map<String, Object> runSubscriptionExpired( + DispatchContext dctx, Map<String, ? extends Object> context) { + String subscriptionId = (String) context.get("subscriptionId"); + Map<String, Object> result = new HashMap<String, Object>(); + if (subscriptionId != null) { + return ServiceUtil.returnSuccess("runSubscriptionExpired service called successfully with subscriptionId " + subscriptionId); + } + return result; + } } Modified: ofbiz/trunk/applications/product/widget/catalog/SubscriptionForms.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/product/widget/catalog/SubscriptionForms.xml?rev=1597235&r1=1597234&r2=1597235&view=diff ============================================================================== --- ofbiz/trunk/applications/product/widget/catalog/SubscriptionForms.xml (original) +++ ofbiz/trunk/applications/product/widget/catalog/SubscriptionForms.xml Sat May 24 04:37:16 2014 @@ -296,6 +296,15 @@ under the License. </entity-options> </drop-down> </field> + <field name="gracePeriodOnExpiryUomId" title="${uiLabelMap.ProductGracePeriodUomId}"> + <drop-down allow-empty="true"> + <entity-options entity-name="Uom" key-field-name="uomId" description="${description}"> + <entity-constraint name="uomTypeId" value="TIME_FREQ_MEASURE"/> + <entity-order-by field-name="description"/> + </entity-options> + </drop-down> + </field> + <field name="serviceNameOnExpiry" ><ignored/></field> <field name="submitButton" title="${uiLabelMap.CommonUpdate}" widget-style="smallSubmit"><submit button-type="button"/></field> </form> Modified: ofbiz/trunk/specialpurpose/ecommerce/data/DemoProduct.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/ecommerce/data/DemoProduct.xml?rev=1597235&r1=1597234&r2=1597235&view=diff ============================================================================== --- ofbiz/trunk/specialpurpose/ecommerce/data/DemoProduct.xml (original) +++ ofbiz/trunk/specialpurpose/ecommerce/data/DemoProduct.xml Sat May 24 04:37:16 2014 @@ -617,8 +617,8 @@ under the License. <!-- test Subscription product, a Gizmo Newsletter --> <Product productId="GZ-NEWS-1MO" productTypeId="DIGITAL_GOOD" primaryProductCategoryId="101" productName="Gizmo Newsletter 1 Month" internalName="Gizmo Newsletter 1 Month Subscription" description="A 1 month subscription to the Gizmo Newsletter: can be used immediately after purchase." longDescription="This newsletter will give you regular updates on the wonderful world of Gizmos!" taxable="Y" chargeShipping="N" autoCreateKeywords="Y" isVirtual="N" isVariant="N" createdDate="2001-05-13 12:00:00.0" createdByUserLogin="admin" lastModifiedDate="2001-05-13 12:00:00.0" lastModifiedByUserLogin="admin"/> - <SubscriptionResource subscriptionResourceId="GZ-NEWS" description="Gizmo Newsletter"/> - <ProductSubscriptionResource productId="GZ-NEWS-1MO" subscriptionResourceId="GZ-NEWS" fromDate="2001-05-13 12:00:00.0" useTime="1" useTimeUomId="TF_mon"/> + <SubscriptionResource subscriptionResourceId="GZ-NEWS" description="Gizmo Newsletter" serviceNameOnExpiry="runSubscriptionExpired"/> + <ProductSubscriptionResource productId="GZ-NEWS-1MO" subscriptionResourceId="GZ-NEWS" fromDate="2001-05-13 12:00:00.0" useTime="1" useTimeUomId="TF_mon" gracePeriodOnExpiry="1" gracePeriodOnExpiryUomId="TF_mon"/> <ProductPrice productId="GZ-NEWS-1MO" productPricePurposeId="PURCHASE" productPriceTypeId="DEFAULT_PRICE" currencyUomId="USD" productStoreGroupId="_NA_" fromDate="2001-05-13 12:00:00.0" price="3.99" createdDate="2001-05-13 12:00:00.0" createdByUserLogin="admin" lastModifiedDate="2001-05-13 12:00:00.0" lastModifiedByUserLogin="admin"/> <ProductPrice productId="GZ-NEWS-1MO" productPricePurposeId="PURCHASE" productPriceTypeId="LIST_PRICE" currencyUomId="USD" productStoreGroupId="_NA_" fromDate="2001-05-13 12:00:00.0" price="5.0" createdDate="2001-05-13 12:00:00.0" createdByUserLogin="admin" lastModifiedDate="2001-05-13 12:00:00.0" lastModifiedByUserLogin="admin"/> <DataResource dataResourceTypeId="ELECTRONIC_TEXT" dataResourceId="GZ-NEWS-1MO-ALT" localeString="en"/> @@ -628,8 +628,8 @@ under the License. <Content contentId="GZ-NEWS-1MO-ALT" contentTypeId="DOCUMENT" dataResourceId="GZ-NEWS-1MO-ALT" localeString="en"/> <Content contentId="CGZ-NEWS-1MO-ALTEN" contentTypeId="DOCUMENT" dataResourceId="DRGZ-NEWS-1MO-ALTEN" localeString="en_US"/> <ContentAssoc contentId="GZ-NEWS-1MO-ALT" contentIdTo="CGZ-NEWS-1MO-ALTEN" contentAssocTypeId="ALTERNATE_LOCALE" fromDate="2011-04-26 12:00:00.0"/> - <ProductContent productId="GZ-NEWS-1MO" contentId="GZ-NEWS-1MO-ALT" productContentTypeId="ALTERNATIVE_URL" fromDate="2001-05-13 12:00:00.0"/> - + <ProductContent productId="GZ-NEWS-1MO" contentId="GZ-NEWS-1MO-ALT" productContentTypeId="ALTERNATIVE_URL" fromDate="2001-05-13 12:00:00.0"/> + <!-- test Digital Download product --> <Product productId="GZ-DIG" productTypeId="DIGITAL_GOOD" primaryProductCategoryId="101" productName="Digital Gizmo" internalName="Digital Gizmo" description="A digital gizmo: can be downloaded immediately after purchase." longDescription="This gizmo is part of an exciting new breed that needs no corporeal form: it is all digital! Buy and download it now!" taxable="Y" chargeShipping="N" autoCreateKeywords="Y" isVirtual="N" isVariant="N" createdDate="2001-05-13 12:00:00.0" createdByUserLogin="admin" lastModifiedDate="2001-05-13 12:00:00.0" lastModifiedByUserLogin="admin"/> <DataResource dataResourceId="GZ-DIG" dataResourceTypeId="OFBIZ_FILE_BIN" mimeTypeId="image/gif" dataResourceName="Digital Gizmo Image" objectInfo="framework/images/webapp/images/ofbiz_logo.gif"/> |
Free forum by Nabble | Edit this page |