blob: 7bb76235504807368be1982403c935506453d388 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<simple-methods xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://ofbiz.apache.org/Simple-Method" xsi:schemaLocation="http://ofbiz.apache.org/Simple-Method http://ofbiz.apache.org/dtds/simple-methods.xsd">
<!-- a method to centralize facility security code, meant to be called in-line with
call-simple-method, and the checkAction and callingMethodName attributes should be in the method context -->
<simple-method method-name="checkFacilityRelatedPermission" short-description="Check Facility Related Permission">
<if-empty field="callingMethodName">
<property-to-field resource="CommonUiLabels" property="CommonPermissionThisOperation" field="callingMethodName"/>
</if-empty>
<if-empty field="checkAction">
<set value="UPDATE" field="checkAction"/>
</if-empty>
<if>
<condition>
<not>
<or>
<if-has-permission permission="CATALOG" action="_${checkAction}"/>
<if-has-permission permission="CATALOG_ADMIN"/>
<if-has-permission permission="FACILITY" action="_${checkAction}"/>
<if-has-permission permission="FACILITY_ADMIN"/>
<and>
<not><if-empty field="alternatePermissionRoot"/></not>
<if-has-permission permission="${alternatePermissionRoot}" action="_${checkAction}"/>
</and>
</or>
</not>
</condition>
<then>
<add-error>
<fail-property resource="ProductUiLabels" property="ProductCatalogCreatePermissionError"/>
</add-error>
</then>
</if>
</simple-method>
<simple-method method-name="facilityGenericPermission" short-description="Main permission logic">
<set field="mainAction" from-field="parameters.mainAction"/>
<if-empty field="mainAction">
<add-error>
<fail-property resource="ProductUiLabels" property="ProductMissingMainActionInPermissionService"/>
</add-error>
<check-errors/>
</if-empty>
<set field="callingMethodName" from-field="parameters.resourceDescription"/>
<set field="checkAction" from-field="parameters.mainAction"/>
<call-simple-method method-name="checkFacilityRelatedPermission"/>
<if-empty field="error_list">
<set field="hasPermission" type="Boolean" value="true"/>
<field-to-result field="hasPermission"/>
<else>
<property-to-field resource="ProductUiLabels" property="ProductFacilityPermissionError" field="failMessage"/>
<set field="hasPermission" type="Boolean" value="false"/>
<field-to-result field="hasPermission"/>
<field-to-result field="failMessage"/>
</else>
</if-empty>
</simple-method>
<simple-method method-name="checkProductFacilityRelatedPermission" short-description="ProductFacility Permission Checking Logic">
<if-empty field="mainAction">
<set field="mainAction" from-field="parameters.mainAction"/>
<if-empty field="mainAction">
<add-error>
<fail-property resource="CommonUiLabels" property="CommonPermissionMainActionAttributeMissing"/>
</add-error>
</if-empty>
</if-empty>
<check-errors/>
<set field="resourceDescription" from-field="parameters.resourceDescription"/>
<if-empty field="resourceDescription">
<property-to-field resource="CommonUiLabels" property="CommonPermissionThisOperation" field="resourceDescription"/>
</if-empty>
<set field="callingMethodName" from-field="resourceDescription"/>
<set field="checkAction" from-field="mainAction"/>
<set field="alternatePermissionRoot" value="FACILITY"/>
<call-simple-method method-name="checkProductRelatedPermission" xml-resource="component://product/minilang/product/product/ProductServices.xml"/>
<if-empty field="error_list">
<set field="hasPermission" type="Boolean" value="true"/>
<field-to-result field="hasPermission"/>
<else>
<property-to-field resource="ProductUiLabels" property="ProductFacilityPermissionError" field="failMessage"/>
<set field="hasPermission" type="Boolean" value="false"/>
<field-to-result field="hasPermission"/>
<field-to-result field="failMessage"/>
</else>
</if-empty>
</simple-method>
<!-- InventoryItem methods -->
<simple-method method-name="createInventoryItem" short-description="Create an InventoryItem">
<!-- Create a lot before -->
<entity-one value-field="product" entity-name="Product">
<field-map field-name="productId" from-field="parameters.productId"/>
</entity-one>
<!-- Check if this product can or not have a lotId -->
<if>
<condition>
<and>
<if-compare operator="equals" value="Mandatory" field="product.lotIdFilledIn" />
<if-empty field="parameters.lotId" />
</and>
</condition>
<then>
<add-error>
<fail-property resource="ProductErrorUiLabels" property="ProductLotIdMandatory"/>
</add-error>
</then>
</if>
<if>
<condition>
<and>
<if-compare operator="equals" value="Forbidden" field="product.lotIdFilledIn" />
<not>
<if-empty field="parameters.lotId" />
</not>
</and>
</condition>
<then>
<add-error>
<fail-property resource="ProductErrorUiLabels" property="ProductLotIdForbidden"/>
</add-error>
</then>
</if>
<check-errors />
<!-- If this InventoryItem is returned by a manufacturing task, don't create a lot -->
<if-compare operator="equals" value="N" field="parameters.isReturned">
<if-not-empty field="parameters.lotId">
<!-- Check if the lot already exists -->
<entity-and entity-name="Lot" list="lotList">
<field-map field-name="lotId" from-field="parameters.lotId" />
</entity-and>
<if-empty field="lotList">
<make-value value-field="lot" entity-name="Lot"/>
<set field="lot.lotId" from-field="parameters.lotId"/>
<create-value value-field="lot"/>
</if-empty>
</if-not-empty>
</if-compare>
<make-value value-field="inventoryItem" entity-name="InventoryItem"/>
<!-- TODO: make sure availableToPromiseTotal and quantityOnHandTotal are not changed -->
<set-nonpk-fields map="parameters" value-field="inventoryItem"/>
<call-simple-method method-name="inventoryItemCheckSetDefaultValues"/>
<check-errors/>
<sequenced-id sequence-name="InventoryItem" field="inventoryItem.inventoryItemId"/>
<create-value value-field="inventoryItem"/>
<field-to-result field="inventoryItem.inventoryItemId" result-name="inventoryItemId"/>
</simple-method>
<simple-method method-name="createInventoryItemCheckSetAtpQoh" short-description="createInventoryItemCheckSetAtpQoh" login-required="false">
<if>
<condition>
<or>
<not><if-empty field="parameters.availableToPromiseTotal"/></not>
<not><if-empty field="parameters.quantityOnHandTotal"/></not>
</or>
</condition>
<then>
<log level="info" message="Got an InventoryItem with ATP/QOH Total with ID ${parameters.inventoryItemId}, creating InventoryItemDetail"/>
<set from-field="parameters.inventoryItemId" field="createDetailMap.inventoryItemId"/>
<set from-field="parameters.availableToPromiseTotal" field="createDetailMap.availableToPromiseDiff"/>
<set from-field="parameters.quantityOnHandTotal" field="createDetailMap.quantityOnHandDiff"/>
<call-service service-name="createInventoryItemDetail" in-map-name="createDetailMap"/>
</then>
</if>
</simple-method>
<simple-method method-name="inventoryItemCheckSetDefaultValues" short-description="Check and, if empty, fills with default values ownerPartyId, currencyUomId, unitCost" login-required="false">
<if-empty field="inventoryItem">
<entity-one entity-name="InventoryItem" value-field="inventoryItem"/>
<set field="updateInventoryItem" value="Y"/>
</if-empty>
<!-- if all the inventoryItem's fields are already filled, return with success -->
<if>
<condition>
<and>
<not><if-empty field="inventoryItem.facilityId"/></not>
<not><if-empty field="inventoryItem.ownerPartyId"/></not>
<not><if-empty field="inventoryItem.currencyUomId"/></not>
<not><if-empty field="inventoryItem.unitCost"/></not>
</and>
</condition>
<then>
<return/>
</then>
</if>
<if-empty field="inventoryItem.facilityId">
<add-error>
<fail-property resource="ProductUiLabels" property="FacilityInventoryItemsMissingFacilityId"/>
</add-error>
<check-errors/>
</if-empty>
<!-- if inventoryItem's ownerPartyId is empty, get the ownerPartyId from the facility -->
<if-empty field="inventoryItem.ownerPartyId">
<get-related-one value-field="inventoryItem" relation-name="Facility" to-value-field="facility"/>
<set field="inventoryItem.ownerPartyId" from-field="facility.ownerPartyId"/>
<!-- if inventoryItem's ownerPartyId is still empty, return an error message -->
<if-empty field="inventoryItem.ownerPartyId">
<add-error>
<fail-property resource="ProductUiLabels" property="FacilityInventoryItemsMissingOwnerPartyId"/>
</add-error>
<check-errors/>
</if-empty>
</if-empty>
<!-- if inventoryItem's currencyUomId is empty, get the currencyUomId
from the party accounting preferences of the owner of the inventory item -->
<if-empty field="inventoryItem.currencyUomId">
<set field="partyAccountingPreferencesCallMap.organizationPartyId" from-field="inventoryItem.ownerPartyId"/>
<call-service service-name="getPartyAccountingPreferences" in-map-name="partyAccountingPreferencesCallMap">
<result-to-field result-name="partyAccountingPreference" field="accPref"/>
</call-service>
<set field="inventoryItem.currencyUomId" from-field="accPref.baseCurrencyUomId"/>
<if-empty field="inventoryItem.currencyUomId">
<property-to-field resource="general" property="currency.uom.id.default" field="inventoryItem.currencyUomId"/>
</if-empty>
<!-- if inventoryItem's currencyUomId is still empty, return an error message -->
<if-empty field="inventoryItem.currencyUomId">
<add-error>
<fail-property resource="ProductUiLabels" property="FacilityInventoryItemsMissingCurrencyId"/>
</add-error>
<check-errors/>
</if-empty>
</if-empty>
<!-- if inventoryItem's unitCost is empty, get the product's standard
cost by calling the getProductCost service -->
<if-empty field="inventoryItem.unitCost">
<set from-field="inventoryItem.productId" field="inputMap.productId"/>
<set from-field="inventoryItem.currencyUomId" field="inputMap.currencyUomId"/>
<set value="EST_STD" field="inputMap.costComponentTypePrefix"/> <!-- TODO: create a new service getProductStdCost that calls getProductCost -->
<call-service service-name="getProductCost" in-map-name="inputMap">
<result-to-field result-name="productCost" field="inventoryItem.unitCost"/>
</call-service>
</if-empty>
<!-- if inventoryItem's unitCost is still empty, or negative return an error message -->
<!-- TODO/WARNING: getProductCost returns 0 even if no std costs are found -->
<if-empty field="inventoryItem.unitCost">
<add-error>
<fail-property resource="ProductUiLabels" property="FacilityInventoryItemsMissingUnitCost"/>
</add-error>
</if-empty>
<check-errors/>
<!-- if you don't want inventory item with unitCost = 0, change the operator
attribute from "less" to "less-equals".
-->
<if-compare field="inventoryItem.unitCost" operator="less" value="0" type="BigDecimal">
<add-error>
<fail-property resource="ProductUiLabels" property="FacilityInventoryItemsNegativeUnitCost"/>
</add-error>
</if-compare>
<check-errors/>
<if-not-empty field="updateInventoryItem">
<store-value value-field="inventoryItem"/>
</if-not-empty>
</simple-method>
<simple-method method-name="updateInventoryItem" short-description="Update an InventoryItem">
<make-value entity-name="InventoryItem" value-field="lookupPKMap"/>
<set-pk-fields map="parameters" value-field="lookupPKMap"/>
<find-by-primary-key map="lookupPKMap" value-field="lookedUpValue"/>
<if-empty field="lookedUpValue.ownerPartyId">
<get-related-one value-field="lookedUpValue" relation-name="Facility" to-value-field="oldFacility"/>
<set field="lookedUpValue.ownerPartyId" from-field="oldFacility.ownerPartyId"/>
</if-empty>
<field-to-result field="lookedUpValue.ownerPartyId" result-name="oldOwnerPartyId"/>
<field-to-result field="lookedUpValue.statusId" result-name="oldStatusId"/>
<field-to-result field="lookedUpValue.productId" result-name="oldProductId"/>
<!-- special handling for the unitCost -->
<if-not-empty field="parameters.unitCost">
<if-compare field="parameters.unitCost" operator="less" value="0.0" type="BigDecimal">
<add-error>
<fail-property resource="ProductUiLabels" property="FacilityInventoryItemsUnitCostCannotBeNegative"/>
</add-error>
<check-errors/>
</if-compare>
</if-not-empty>
<set field="oldUnitCost" from-field="lookedUpValue.unitCost"/>
<if-not-empty field="parameters.lotId">
<!-- Check if the lot already exists -->
<entity-and entity-name="Lot" list="lotList">
<field-map field-name="lotId" from-field="parameters.lotId" />
</entity-and>
<if-empty field="lotList">
<make-value value-field="lot" entity-name="Lot"/>
<set field="lot.lotId" from-field="parameters.lotId"/>
<create-value value-field="lot"/>
</if-empty>
</if-not-empty>
<set-nonpk-fields map="parameters" value-field="lookedUpValue"/>
<store-value value-field="lookedUpValue"/>
<!-- if the unit cost is changed create an InventoryItemDetail to keep track of unit cost history -->
<if-not-empty field="parameters.unitCost">
<if-compare-field field="parameters.unitCost" to-field="oldUnitCost" operator="not-equals">
<set field="createInventoryItemDetailInMap.inventoryItemId" from-field="lookedUpValue.inventoryItemId"/>
<set field="createInventoryItemDetailInMap.unitCost" from-field="parameters.unitCost"/>
<call-service service-name="createInventoryItemDetail" in-map-name="createInventoryItemDetailInMap"/>
</if-compare-field>
</if-not-empty>
</simple-method>
<simple-method method-name="createInventoryItemStatus" short-description="Create an inventory item status record">
<now-timestamp field="nowTimestamp"/>
<!-- find the most recent InventoryItemStatus record and set the statusEndDatetime -->
<entity-and entity-name="InventoryItemStatus" list="oldInventoryItemStatusList">
<field-map field-name="inventoryItemId" from-field="parameters.inventoryItemId"/>
<order-by field-name="-statusDatetime"/>
</entity-and>
<first-from-list list="oldInventoryItemStatusList" entry="oldInventoryItemStatus"/>
<if-not-empty field="oldInventoryItemStatus">
<set field="oldInventoryItemStatus.statusEndDatetime" from-field="nowTimestamp"/>
<store-value value-field="oldInventoryItemStatus"/>
</if-not-empty>
<make-value value-field="inventoryItemStatus" entity-name="InventoryItemStatus"/>
<set-nonpk-fields map="parameters" value-field="inventoryItemStatus"/>
<set-pk-fields map="parameters" value-field="inventoryItemStatus"/>
<set field="inventoryItemStatus.statusDatetime" from-field="nowTimestamp"/>
<set field="inventoryItemStatus.changeByUserLoginId" from-field="userLogin.userLoginId"/>
<!-- make sure the current productId is set, if not passed in look up the current value -->
<if-empty field="inventoryItemStatus.productId">
<entity-one entity-name="InventoryItem" value-field="inventoryItem"/>
<set field="inventoryItemStatus.productId" from-field="inventoryItem.productId"/>
</if-empty>
<create-value value-field="inventoryItemStatus"/>
</simple-method>
<simple-method method-name="createInventoryItemDetail" short-description="Create an InventoryItemDetail">
<make-value value-field="newEntity" entity-name="InventoryItemDetail"/>
<set from-field="parameters.inventoryItemId" field="newEntity.inventoryItemId"/>
<!-- NOTE DEJ20070927: not using make-next-seq-id because a single InventoryItem may see traffic from lots of threads at the same time, and make-next-seq-id doesn't do well with that <make-next-seq-id seq-field-name="inventoryItemDetailSeqId" value-field="newEntity" increment-by="1" numeric-padding="4"/> -->
<sequenced-id sequence-name="InventoryItemDetail" field="newEntity.inventoryItemDetailSeqId"/>
<field-to-result field="newEntity.inventoryItemDetailSeqId" result-name="inventoryItemDetailSeqId"/>
<set-nonpk-fields map="parameters" value-field="newEntity"/>
<!-- set the effectiveDate; if from an ItemIssuance lookup the issuedDateTime -->
<if-not-empty field="parameters.itemIssuanceId">
<entity-one entity-name="ItemIssuance" value-field="itemIssuance"/>
<set field="newEntity.effectiveDate" from-field="itemIssuance.issuedDateTime"/>
<else>
<now-timestamp field="newEntity.effectiveDate"/>
</else>
</if-not-empty>
<!-- if availableToPromiseDiff or quantityOnHandDiff are empty set to 0 -->
<if-empty field="newEntity.availableToPromiseDiff"><set field="newEntity.availableToPromiseDiff" value="0" type="BigDecimal"/></if-empty>
<if-empty field="newEntity.quantityOnHandDiff"><set field="newEntity.quantityOnHandDiff" value="0" type="BigDecimal"/></if-empty>
<if-empty field="newEntity.accountingQuantityDiff"><set field="newEntity.accountingQuantityDiff" value="0" type="BigDecimal"/></if-empty>
<create-value value-field="newEntity"/>
</simple-method>
<simple-method method-name="updateInventoryItemFromDetail" short-description="Update an InventoryItem From the Associated Detail Records" login-required="false">
<entity-one entity-name="InventoryItem" value-field="inventoryItem"/>
<entity-one entity-name="InventoryItemDetailSummary" value-field="inventoryItemDetailSummary"/>
<set field="inventoryItem.availableToPromiseTotal" from-field="inventoryItemDetailSummary.availableToPromiseTotal"/>
<set field="inventoryItem.quantityOnHandTotal" from-field="inventoryItemDetailSummary.quantityOnHandTotal"/>
<set field="inventoryItem.accountingQuantityTotal" from-field="inventoryItemDetailSummary.accountingQuantityTotal"/>
<store-value value-field="inventoryItem"/>
</simple-method>
<simple-method method-name="updateSerializedInventoryTotals" short-description="Update the totals on serialized inventory">
<entity-one entity-name="InventoryItem" value-field="inventoryItem"/>
<if-compare field="inventoryItem.inventoryItemTypeId" value="SERIALIZED_INV_ITEM" operator="equals">
<if>
<!-- available -->
<condition>
<and>
<if-compare field="inventoryItem.statusId" value="INV_AVAILABLE" operator="equals"/>
<or>
<if-compare field="inventoryItem.availableToPromiseTotal" operator="not-equals" value="1" type="BigDecimal"/>
<if-compare field="inventoryItem.quantityOnHandTotal" operator="not-equals" value="1" type="BigDecimal"/>
</or>
</and>
</condition>
<then>
<set field="inventoryItem.availableToPromiseTotal" value="1" type="BigDecimal"/>
<set field="inventoryItem.quantityOnHandTotal" value="1" type="BigDecimal"/>
<log level="always" message="In updateSerializedInventoryTotals Storing totals for item [${inventoryItem.inventoryItemId}] INV_AVAIABLE [1/1]"/>
<store-value value-field="inventoryItem"/>
</then>
<!-- delivered -->
<else-if>
<condition>
<and>
<if-compare field="inventoryItem.statusId" value="INV_DELIVERED" operator="equals"/>
<or>
<if-compare field="inventoryItem.availableToPromiseTotal" operator="not-equals" value="0" type="BigDecimal"/>
<if-compare field="inventoryItem.quantityOnHandTotal" operator="not-equals" value="0" type="BigDecimal"/>
</or>
</and>
</condition>
<then>
<set field="inventoryItem.availableToPromiseTotal" value="0" type="BigDecimal"/>
<set field="inventoryItem.quantityOnHandTotal" value="0" type="BigDecimal"/>
<log level="always" message="In updateSerializedInventoryTotals Storing totals [${inventoryItem.inventoryItemId}] for INV_DELIVERED [0/0]"/>
<store-value value-field="inventoryItem"/>
</then>
</else-if>
<!-- any promised; or on-hand but not available status -->
<else-if>
<condition>
<and>
<if-compare field="inventoryItem.statusId" operator="not-equals" value="INV_AVAILABLE"/>
<if-compare field="inventoryItem.statusId" operator="not-equals" value="INV_DELIVERED"/>
<or>
<if-compare field="inventoryItem.availableToPromiseTotal" operator="not-equals" value="0" type="BigDecimal"/>
<if-compare field="inventoryItem.quantityOnHandTotal" operator="not-equals" value="1" type="BigDecimal"/>
</or>
</and>
</condition>
<then>
<set field="inventoryItem.availableToPromiseTotal" value="0" type="BigDecimal"/>
<set field="inventoryItem.quantityOnHandTotal" value="1" type="BigDecimal"/>
<log level="always" message="In updateSerializedInventoryTotals Storing totals [${inventoryItem.inventoryItemId}] for other status [0/1]"/>
<store-value value-field="inventoryItem"/>
</then>
</else-if>
</if>
</if-compare>
</simple-method>
<simple-method method-name="updateOldInventoryToDetailAll" short-description="Update Old Inventory To Detail All">
<!-- find all InventoryItem records where oldQuantityOnHand or oldAvailableToPromise are not null -->
<entity-condition entity-name="InventoryItem" list="inventoryItemList">
<condition-list combine="or">
<condition-expr field-name="oldQuantityOnHand" operator="not-equals" value=""/>
<condition-expr field-name="oldAvailableToPromise" operator="not-equals" value=""/>
</condition-list>
</entity-condition>
<iterate list="inventoryItemList" entry="inventoryItem">
<set from-field="inventoryItem" field="callServiceMap.inventoryItem"/>
<call-service service-name="updateOldInventoryToDetailSingle" in-map-name="callServiceMap"/>
<clear-field field="callServiceMap.inventoryItem"/>
</iterate>
</simple-method>
<simple-method method-name="updateOldInventoryToDetailSingle" short-description="Update Old Inventory To Detail Single">
<!-- for each create an InventoryItemDetail representing the old QOH or ATP value, then null those fields -->
<set from-field="parameters.inventoryItem.inventoryItemId" field="createDetailMap.inventoryItemId"/>
<set from-field="parameters.inventoryItem.oldAvailableToPromise" field="createDetailMap.availableToPromiseDiff"/>
<set from-field="parameters.inventoryItem.oldQuantityOnHand" field="createDetailMap.quantityOnHandDiff"/>
<call-service service-name="createInventoryItemDetail" in-map-name="createDetailMap"/>
<clear-field field="parameters.inventoryItem.oldAvailableToPromise"/>
<clear-field field="parameters.inventoryItem.oldQuantityOnHand"/>
<store-value value-field="parameters.inventoryItem"/>
</simple-method>
<simple-method method-name="checkProductInventoryDiscontinuation" short-description="Check Product Inventory Discontinuation" login-required="false">
<set from-field="parameters.productId" field="productIdMap.productId"/>
<find-by-primary-key entity-name="Product" map="productIdMap" value-field="product"/>
<now-timestamp field="nowTimestamp"/>
<!-- if discontinueProductSales field is empty and the product is a variant, get the fieldcontent from the virtual product -->
<if-not-empty field="product">
<if-compare field="product.isVariant" value="Y" operator="equals">
<!-- retrieve related virtual product because also to be used later -->
<set field="getAssoc.productIdTo" from-field="product.productId"/>
<set field="getAssoc.productAssocTypeId" value="PRODUCT_VARIANT"/>
<find-by-and entity-name="ProductAssoc" map="getAssoc" list="assocs"/>
<if-not-empty field="assocs">
<filter-list-by-date list="assocs" to-list="assocsDate"/>
<first-from-list list="assocsDate" entry="assoc"/>
<if-not-empty field="assoc">
<get-related-one value-field="assoc" relation-name="MainProduct" to-value-field="virtProduct"/>
<if-empty field="product.salesDiscWhenNotAvail">
<set field="product.salesDiscWhenNotAvail" from-field="virtProduct.salesDiscWhenNotAvail"/>
</if-empty>
</if-not-empty>
</if-not-empty>
</if-compare>
</if-not-empty>
<!-- before checking inventory availability see if the product is already discontinued, and discontinued in the past (if in the future, still check availability and discontinue now if necessary) -->
<if>
<condition>
<and>
<not><if-empty field="product"/></not>
<if-compare field="product.salesDiscWhenNotAvail" operator="equals" value="Y"/>
<or>
<if-empty field="product.salesDiscontinuationDate"/>
<if-compare-field field="product.salesDiscontinuationDate" to-field="nowTimestamp" operator="greater" type="Timestamp"/>
</or>
</and>
</condition>
<then>
<!-- now for the real fun, get the inventory available if is less-equal to zero discontinue product -->
<call-service service-name="getProductInventoryAvailable" in-map-name="productIdMap">
<result-to-field result-name="availableToPromiseTotal"/>
</call-service>
<if-compare field="availableToPromiseTotal" operator="less-equals" value="0" type="BigDecimal">
<set from-field="parameters.productId" field="discontinueProductSalesMap.productId"/>
<call-service service-name="discontinueProductSales" in-map-name="discontinueProductSalesMap"/>
</if-compare>
<!-- check if related virtual product has no variant left, if yes discontinue the virtual product too when salesDiscWhenNotAvail is 'Y'-->
<if-not-empty field="virtProduct">
<if-compare field="virtProduct.salesDiscWhenNotAvail" operator="equals" value="Y">
<set field="getFromAssoc.productId" from-field="virtProduct.productId"/>
<set field="getFromAssoc.productAssocTypeId" value="PRODUCT_VARIANT"/>
<find-by-and entity-name="ProductAssoc" map="getFromAssoc" list="assocs"/>
<filter-list-by-date list="assocs" to-list="assocsDate"/>
<if-empty field="assocsDate">
<set from-field="virtProduct.productId" field="discontinueProductSalesMap.productId"/>
<call-service service-name="discontinueProductSales" in-map-name="discontinueProductSalesMap"/>
</if-empty>
</if-compare>
</if-not-empty>
</then>
</if>
</simple-method>
<simple-method method-name="createInventoryItemVariance" short-description="Create an InventoryItemVariance">
<!-- add changes to availableToPromise and quantityOnHand -->
<make-value value-field="inventoryItemLookup" entity-name="InventoryItem"/>
<set-pk-fields map="parameters" value-field="inventoryItemLookup"/>
<find-by-primary-key map="inventoryItemLookup" value-field="inventoryItem"/>
<if-compare field="inventoryItem.inventoryItemTypeId" operator="not-equals" value="NON_SERIAL_INV_ITEM">
<string-to-list string="Can only create an InventoryItemVariance for a Non-Serialized Inventory Item" list="error_list"/>
</if-compare>
<check-errors/>
<!-- instead of updating InventoryItem, add an InventoryItemDetail -->
<set from-field="parameters.inventoryItemId" field="createDetailMap.inventoryItemId"/>
<set from-field="parameters.physicalInventoryId" field="createDetailMap.physicalInventoryId"/>
<set from-field="parameters.availableToPromiseVar" field="createDetailMap.availableToPromiseDiff"/>
<set from-field="parameters.quantityOnHandVar" field="createDetailMap.quantityOnHandDiff"/>
<set from-field="parameters.varianceReasonId" field="createDetailMap.reasonEnumId"/>
<set from-field="parameters.comments" field="createDetailMap.description"/>
<call-service service-name="createInventoryItemDetail" in-map-name="createDetailMap"/>
<make-value value-field="newEntity" entity-name="InventoryItemVariance"/>
<set-pk-fields map="parameters" value-field="newEntity"/>
<set-nonpk-fields map="parameters" value-field="newEntity"/>
<create-value value-field="newEntity"/>
<!-- TODO: (possibly a big deal?) check to see if any reserved inventory needs to be changed because of a change in availableToPromise -->
<!-- TODO: make sure availableToPromise is never greater than the quantityOnHand? -->
</simple-method>
<simple-method method-name="createPhysicalInventoryAndVariance" short-description="Create a PhysicalInventory and an InventoryItemVariance">
<set-service-fields service-name="createPhysicalInventory" map="parameters" to-map="createPhysicalInventoryMap"/>
<call-service service-name="createPhysicalInventory" in-map-name="createPhysicalInventoryMap">
<result-to-field result-name="physicalInventoryId" field="parameters.physicalInventoryId"/>
<result-to-result result-name="physicalInventoryId" service-result-name="physicalInventoryId"/>
</call-service>
<set-service-fields service-name="createInventoryItemVariance" map="parameters" to-map="createInventoryItemVarianceMap"/>
<call-service service-name="createInventoryItemVariance" in-map-name="createInventoryItemVarianceMap"/>
</simple-method>
<!-- ================================================================ -->
<!-- Check/Reserve Inventory Services -->
<!-- ================================================================ -->
<simple-method method-name="getProductInventoryAvailable" short-description="Get Inventory Available for a Product" login-required="false" use-transaction="true">
<!--
this method can be called with some optional parameters:
-facilityId
-partyId
-locationSeqId
-containerId
If the service definitions are used then only some of these will ever be specified, or none of them.
Whatever it is called with, it will basically get a list of InventoryItems and total the available amount.
-->
<!-- <log level="info" message="Getting inventory available to promise count; parameters are: ${parameters}"/> -->
<!-- FIXME: this is an hack to get all the items with a null location:
if the parameters.locationSeqId string is equal to "nullField" then
set the lookupFieldMap.locationSeqId to null
-->
<if-compare field="parameters.locationSeqId" operator="equals" value="nullField">
<set from-field="nullField" field="lookupFieldMap.locationSeqId"/>
</if-compare>
<set from-field="parameters.inventoryItemId" field="lookupFieldMap.inventoryItemId"/>
<set from-field="parameters.productId" field="lookupFieldMap.productId"/>
<set from-field="parameters.facilityId" field="lookupFieldMap.facilityId"/>
<set from-field="parameters.partyId" field="lookupFieldMap.partyId"/>
<set from-field="parameters.locationSeqId" field="lookupFieldMap.locationSeqId"/>
<set from-field="parameters.containerId" field="lookupFieldMap.containerId"/>
<set from-field="parameters.lotId" field="lookupFieldMap.lotId"/>
<!-- we might get away with a cache here since real serious errors will occur during the reservation service... but only if we need the speed -->
<if-compare field="parameters.useCache" operator="equals" value="true" type="Boolean">
<!-- if caching was requested, don't use an iterator -->
<find-by-and entity-name="InventoryItem" map="lookupFieldMap" list="inventoryItems" use-cache="true"/>
<else>
<find-by-and entity-name="InventoryItem" map="lookupFieldMap" list="inventoryItems" use-iterator="true" use-cache="false"/>
</else>
</if-compare>
<set field="parameters.availableToPromiseTotal" value="0" type="BigDecimal"/>
<set field="parameters.quantityOnHandTotal" value="0" type="BigDecimal"/>
<iterate list="inventoryItems" entry="inventoryItem">
<!-- NOTE: this code no longer distinguishes between serialized and non-serialized because both now have availableToPromiseTotal and quantityOnHandTotal populated (for serialized are based on status, non-serialized are based on InventoryItemDetail) -->
<if>
<condition>
<or>
<and>
<not><if-empty field="parameters.statusId"/></not>
<if-compare-field field="parameters.statusId" operator="equals" to-field="inventoryItem.statusId"/>
</and>
<and>
<if-empty field="parameters.statusId"/>
<or>
<if-empty field="inventoryItem.statusId"/>
<if-compare field="inventoryItem.statusId" operator="equals" value="INV_AVAILABLE"/>
<if-compare field="inventoryItem.statusId" operator="equals" value="INV_NS_RETURNED"/>
<if-compare field="inventoryItem.inventoryItemTypeId" operator="equals" value="SERIALIZED_INV_ITEM"/><!-- status is reflected in total fields -->
</or>
</and>
</or>
</condition>
<then>
<set field="parameters.quantityOnHandTotal" value="${parameters.quantityOnHandTotal + inventoryItem.quantityOnHandTotal}" type="BigDecimal"/>
<set field="parameters.availableToPromiseTotal" value="${parameters.availableToPromiseTotal + inventoryItem.availableToPromiseTotal}" type="BigDecimal"/>
</then>
</if>
</iterate>
<field-to-result field="parameters.availableToPromiseTotal" result-name="availableToPromiseTotal"/>
<field-to-result field="parameters.quantityOnHandTotal" result-name="quantityOnHandTotal"/>
</simple-method>
<simple-method method-name="countProductInventoryOnHand" short-description="Count Inventory On Hand for a Product constrained by a facilityId at a given date." use-transaction="false">
<entity-condition entity-name="InventoryItemDetailForSum" list="inventoryItemDetailTotals">
<condition-list combine="and">
<condition-expr field-name="effectiveDate" operator="less-equals" from-field="parameters.inventoryCountDate"/>
<condition-expr field-name="facilityId" operator="equals" from-field="parameters.facilityId"/>
<condition-expr field-name="productId" operator="equals" from-field="parameters.productId"/>
</condition-list>
<select-field field-name="quantityOnHandSum"/>
</entity-condition>
<first-from-list list="inventoryItemDetailTotals" entry="inventoryItemDetailTotal"/>
<set field="quantityOnHandTotal" from-field="inventoryItemDetailTotal.quantityOnHandSum" type="BigDecimal" default-value="0"/>
<field-to-result field="quantityOnHandTotal" result-name="quantityOnHandTotal"/>
</simple-method>
<simple-method method-name="countProductInventoryShippedForSales" short-description="Count Inventory Shipped for Sales Orders for a Product constrained by a facilityId in a given date range." use-transaction="false">
<if-empty field="parameters.thruDate">
<now-timestamp field="parameters.thruDate"/>
</if-empty>
<entity-condition entity-name="InventoryItemDetailForSum" list="inventoryItemDetailTotals">
<condition-list combine="and">
<condition-expr field-name="effectiveDate" operator="greater-equals" from-field="parameters.fromDate"/>
<condition-expr field-name="effectiveDate" operator="less" from-field="parameters.thruDate"/>
<condition-expr field-name="facilityId" operator="equals" from-field="parameters.facilityId"/>
<condition-expr field-name="productId" operator="equals" from-field="parameters.productId"/>
<condition-expr field-name="orderId" operator="not-equals" from-field="nullField"/>
<condition-expr field-name="quantityOnHandDiff" operator="less" value="0"/>
</condition-list>
<select-field field-name="quantityOnHandSum"/>
</entity-condition>
<first-from-list list="inventoryItemDetailTotals" entry="inventoryItemDetailTotal"/>
<set field="quantityOnHandTotal" from-field="${inventoryItemDetailTotal.quantityOnHandSum * -1}" type="BigDecimal" default-value="0"/>
<field-to-result field="quantityOnHandTotal" result-name="quantityOnHandTotal"/>
</simple-method>
<simple-method method-name="getMktgPackagesAvailable" short-description="Get Marketing Packages Available From Components In Inventory" login-required="false" use-transaction="false">
<set field="availableToPromiseTotal" value="0" type="BigDecimal"/>
<set field="quantityOnHandTotal" value="0" type="BigDecimal"/>
<set from-field="parameters.productId" field="lookupMktgPkgParams.productId"/>
<entity-one entity-name="Product" value-field="product"/>
<set field="isMarketingPkgAuto" value="${groovy: org.apache.ofbiz.entity.util.EntityTypeUtil.hasParentType(delegator, 'ProductType', 'productTypeId', product.productTypeId, 'parentTypeId', 'MARKETING_PKG_AUTO')}" type="Boolean"/>
<if-compare field="isMarketingPkgAuto" operator="equals" value="true" type="Boolean">
<set field="productIdMap.productId" from-field="product.productId"/>
<call-service service-name="getProductInventoryAvailable" in-map-name="productIdMap">
<result-to-field result-name="availableToPromiseTotal"/>
<result-to-field result-name="quantityOnHandTotal"/>
</call-service>
<else>
<set value="PRODUCT_COMPONENT" field="lookupMktgPkgParams.type"/>
<call-service service-name="getAssociatedProducts" in-map-name="lookupMktgPkgParams">
<result-to-field result-name="assocProducts"/>
</call-service>
</else>
</if-compare>
<!-- if there are any components, then the ATP and QOH are based on the quantities of those component
products and found with another service -->
<if-not-empty field="assocProducts">
<set from-field="assocProducts" field="inventoryByAssocProductsParams.assocProducts"/>
<set from-field="parameters.facilityId" field="inventoryByAssocProductsParams.facilityId"/>
<set from-field="parameters.statusId" field="inventoryByAssocProductsParams.statusId"/>
<call-service service-name="getProductInventoryAvailableFromAssocProducts" in-map-name="inventoryByAssocProductsParams">
<result-to-field result-name="quantityOnHandTotal"/>
<result-to-field result-name="availableToPromiseTotal"/>
</call-service>
</if-not-empty>
<field-to-result field="availableToPromiseTotal"/>
<field-to-result field="quantityOnHandTotal"/>
</simple-method>
<!--
Code in balanceInventoryItems service was doing same job which reassignInventoryReservations service is doing. Purpose of both the services are same. In fact reassignInventoryReservations service does better job of
reserving items for an order. 1) It takes into account the order priority, currentPromisedDate, reservedDateTime and sequenceId. where as balanceInventoryItems was prioritizing orders
based on reservedDatetime and sequenceId. 2) reassignInventoryReservations excludes items with sufficient inventory, where as balanceInventoryItems also pulls up order items which have sufficient inventory.
Calling reassignInventoryReservations from balanceInventoryItems. balanceInventoryItems can be deleted, but not deleting it because its used in many places in OFBiz.
To DO: We can delete balanceInventoryItems in future and replace it with reassignInventoryReservations every where.
-->
<simple-method method-name="balanceInventoryItems" short-description="Balances available-to-promise on inventory items">
<entity-one entity-name="InventoryItem" value-field="inventoryItem"/>
<set field="reassignInventoryReservationsCtx.productId" from-field="inventoryItem.productId"/>
<set field="reassignInventoryReservationsCtx.facilityId" from-field="inventoryItem.facilityId"/>
<set field="reassignInventoryReservationsCtx.fromDate" from-field="nowTimestamp"/>
<call-service service-name="reassignInventoryReservations" in-map-name="reassignInventoryReservationsCtx"/>
</simple-method>
<simple-method method-name="reassignInventoryReservations" short-description="Balances available-to-promise on inventory items">
<entity-condition entity-name="OrderItemShipGrpInvResAndItem" list="relatedRes">
<condition-list combine="and">
<condition-expr field-name="productId" operator="equals" from-field="parameters.productId"/>
<condition-expr field-name="facilityId" operator="equals" from-field="parameters.facilityId"/>
<condition-expr field-name="inventoryItemTypeId" operator="equals" value="NON_SERIAL_INV_ITEM"/>
<condition-list combine="or">
<condition-expr field-name="currentPromisedDate" operator="greater" from-field="parameters.fromDate" ignore-if-null="true"/>
<condition-list combine="or">
<condition-expr field-name="quantityNotAvailable" operator="greater" value="0"/>
<condition-expr field-name="availableToPromiseTotal" operator="equals" from-field="nullField"/>
<condition-expr field-name="availableToPromiseTotal" operator="equals" value=""/>
<condition-expr field-name="availableToPromiseTotal" operator="less" value="0"/>
</condition-list>
</condition-list>
</condition-list>
<order-by field-name="priority"/>
<order-by field-name="currentPromisedDate"/>
<order-by field-name="reservedDatetime"/>
<order-by field-name="sequenceId"/>
</entity-condition>
<iterate list="relatedRes" entry="oneRelatedRes">
<entity-condition entity-name="PicklistAndBinAndItem" list="picklistItemList">
<condition-list combine="and">
<condition-expr field-name="orderId" from-field="oneRelatedRes.orderId"/>
<condition-expr field-name="shipGroupSeqId" from-field="oneRelatedRes.shipGroupSeqId"/>
<condition-expr field-name="orderItemSeqId" from-field="oneRelatedRes.orderItemSeqId"/>
<condition-expr field-name="inventoryItemId" from-field="oneRelatedRes.inventoryItemId"/>
<condition-expr field-name="statusId" operator="not-equals" value="PICKLIST_CANCELLED"/>
<condition-expr field-name="statusId" operator="not-equals" value="PICKLIST_PICKED"/>
</condition-list>
</entity-condition>
<!-- only cancel/re-reserve when there are no picklists associated; this will prevent
orders appearing on duplicate pick lists -->
<if-empty field="picklistItemList">
<log level="info" message="Order [${oneRelatedRes.orderId}] was not found on any picklist for InventoryItem [${oneRelatedRes.inventoryItemId}]"/>
<if>
<condition>
<and>
<if-compare-field field="parameters.priorityOrderId" to-field="oneRelatedRes.orderId" operator="equals"/>
<if-compare-field field="parameters.priorityOrderItemSeqId" to-field="oneRelatedRes.orderItemSeqId" operator="equals"/>
</and>
</condition>
<then>
<field-to-list field="oneRelatedRes" list="privilegedReservations"/>
</then>
<else>
<field-to-list field="oneRelatedRes" list="reservations"/>
</else>
</if>
</if-empty>
</iterate>
<list-to-list list="privilegedReservations" to-list="allReservations"/>
<list-to-list list="reservations" to-list="allReservations"/>
<!-- FIRST, cancel all the reservations -->
<iterate list="allReservations" entry="oisgir">
<clear-field field="cancelOisgirMap"/>
<set field="cancelOisgirMap.orderId" from-field="oisgir.orderId"/>
<set field="cancelOisgirMap.orderItemSeqId" from-field="oisgir.orderItemSeqId"/>
<set field="cancelOisgirMap.inventoryItemId" from-field="oisgir.inventoryItemId"/>
<set field="cancelOisgirMap.shipGroupSeqId" from-field="oisgir.shipGroupSeqId"/>
<call-service service-name="cancelOrderItemShipGrpInvRes" in-map-name="cancelOisgirMap"/>
</iterate>
<!-- THEN, re-reserve the cancelled items -->
<iterate list="allReservations" entry="oisgir">
<!-- maintain a Set (in a Map) of orderIds that we have reallocated for, but only if they had some quantityNotReserved -->
<if-not-empty field="oisgir.quantityNotAvailable">
<if-compare field="oisgir.quantityNotAvailable" operator="greater" value="0" type="BigDecimal">
<set field="touchedOrderIdMap[oisgir.orderId]" value="Y"/>
<log level="verbose" message="Adding ${oisgir.orderId} to touchedOrderIdMap"/>
</if-compare>
</if-not-empty>
<entity-one entity-name="OrderHeader" value-field="orderHeader">
<field-map field-name="orderId" from-field="oisgir.orderId"/>
</entity-one>
<!-- require inventory is N because it had to be N to begin with to have a negative ATP -->
<clear-field field="resMap"/>
<set field="resMap.productId" from-field="parameters.productId"/>
<set field="resMap.orderId" from-field="oisgir.orderId"/>
<set field="resMap.orderItemSeqId" from-field="oisgir.orderItemSeqId"/>
<set field="resMap.quantity" from-field="oisgir.quantity"/>
<set field="resMap.reservedDatetime" from-field="oisgir.reservedDatetime"/>
<set field="resMap.reserveOrderEnumId" from-field="oisgir.reserveOrderEnumId"/>
<set field="resMap.requireInventory" value="N"/>
<set field="resMap.shipGroupSeqId" from-field="oisgir.shipGroupSeqId"/>
<set field="resMap.sequenceId" from-field="oisgir.sequenceId"/>
<set field="resMap.facilityId" from-field="parameters.facilityId"/>
<set field="resMap.priority" from-field="orderHeader.priority"/>
<log level="info" message="Re-reserving product [${resMap.productId}] for order item [${resMap.orderId}:${resMap.orderItemSeqId}] quantity [${resMap.quantity}]; facility [${parameters.facilityId}]"/>
<call-service service-name="reserveProductInventoryByFacility" in-map-name="resMap"/>
</iterate>
<!-- now go through touchedOrderIdMap keys and make a Set/Map of orderIds that are no longer on back-order -->
<iterate-map key="touchedOrderId" value="throwAwayValue" map="touchedOrderIdMap">
<set field="checkOrderIsOnBackOrderMap.orderId" from-field="touchedOrderId"/>
<call-service service-name="checkOrderIsOnBackOrder" in-map-name="checkOrderIsOnBackOrderMap">
<result-to-field result-name="isBackOrder"/>
</call-service>
<if-compare field="isBackOrder" operator="equals" value="false" type="Boolean">
<set field="noLongerOnBackOrderIdMap[touchedOrderId]" value="Y"/>
</if-compare>
</iterate-map>
<if-not-empty field="noLongerOnBackOrderIdMap">
<call-object-method obj-field="noLongerOnBackOrderIdMap" method-name="keySet" ret-field="noLongerOnBackOrderIdSet"/>
<field-to-result field="noLongerOnBackOrderIdSet"/>
</if-not-empty>
</simple-method>
<simple-method method-name="balanceOrderItemsWithNegativeReservations" short-description="To balance order items with negative reservations">
<entity-one entity-name="OrderHeader" value-field="orderHeader" auto-field-map="true"/>
<get-related-one value-field="orderHeader" relation-name="ProductStore" to-value-field="productStore"/>
<if-compare field="productStore.balanceResOnOrderCreation" operator="equals" value="Y">
<entity-condition entity-name="OrderItemAndShipGrpInvResAndItem" list="oisgirais">
<condition-list combine="and">
<condition-expr field-name="orderId" operator="equals" from-field="parameters.orderId"/>
<condition-expr field-name="quantityNotAvailable" operator="greater" value="0"/>
<condition-expr field-name="quantityNotAvailable" operator="not-equals" value=""/>
<condition-expr field-name="quantityNotAvailable" operator="not-equals" from-field="nullField"/>
</condition-list>
</entity-condition>
<iterate list="oisgirais" entry="oisgir">
<set field="orderItems[oisgir.orderItemSeqId]" from-field="oisgir"/>
</iterate>
<now-timestamp field="nowTimestamp"/>
<iterate-map key="orderItemSeqId" value="oisgir" map="orderItems">
<set field="reassignInventoryReservationsCtx.productId" from-field="oisgir.productId"/>
<set field="reassignInventoryReservationsCtx.facilityId" from-field="oisgir.facilityId"/>
<if-not-empty field="oisgir.shipBeforeDate">
<set field="reassignInventoryReservationsCtx.fromDate" from-field="oisgir.shipBeforeDate"/>
<else>
<set field="reassignInventoryReservationsCtx.fromDate" from-field="nowTimestamp"/>
</else>
</if-not-empty>
<call-service service-name="reassignInventoryReservations" in-map-name="reassignInventoryReservationsCtx"/>
</iterate-map>
<else>
<log level="info" message="Not reassigning the reservations because productStore.balanceResOnOrderCreation is set to N or null."/>
</else>
</if-compare>
</simple-method>
<!-- Inventory Transfer Services -->
<simple-method method-name="createInventoryTransfer" short-description="Create an Inventory Transfer">
<make-value value-field="newEntity" entity-name="InventoryTransfer"/>
<set-nonpk-fields map="parameters" value-field="newEntity"/>
<sequenced-id sequence-name="InventoryTransfer" field="newEntity.inventoryTransferId"/>
<field-to-result field="newEntity.inventoryTransferId" result-name="inventoryTransferId"/>
<create-value value-field="newEntity"/>
</simple-method>
<simple-method method-name="updateInventoryTransfer" short-description="Update an Inventory Transfer">
<set from-field="parameters.inventoryTransferId" field="lookupPKMap.inventoryTransferId"/>
<find-by-primary-key entity-name="InventoryTransfer" map="lookupPKMap" value-field="inventoryTransfer"/>
<if-not-empty field="parameters.statusId">
<if-compare-field field="parameters.statusId" to-field="inventoryTransfer.statusId" operator="not-equals">
<!-- make sure a StatusValidChange record exists, if not return error -->
<entity-one entity-name="StatusValidChange" value-field="checkStatusValidChange" auto-field-map="false">
<field-map field-name="statusId" from-field="inventoryTransfer.statusId"/>
<field-map field-name="statusIdTo" from-field="parameters.statusId"/>
</entity-one>
<if-empty field="checkStatusValidChange">
<set value="ERROR: Changing the status from ${inventoryTransfer.statusId} to ${parameters.statusId} is not allowed." field="error_list[]"/>
</if-empty>
<check-errors/>
</if-compare-field>
</if-not-empty>
<set-nonpk-fields map="parameters" value-field="inventoryTransfer"/>
<store-value value-field="inventoryTransfer"/>
</simple-method>
<simple-method method-name="createInventoryTransfersForProduct" short-description="Create inventory transfers for the given product and quantity. Return the units not available for transfers.">
<now-timestamp field="nowTimestamp"/>
<!-- check the product; make sure its a physical item -->
<entity-one entity-name="Product" value-field="product"/>
<entity-one entity-name="Facility" value-field="facility" use-cache="true"/>
<get-related-one value-field="product" relation-name="ProductType" to-value-field="productType"/>
<if-compare field="productType.isPhysical" operator="equals" value="N">
<set field="quantityNotTransferred" value="0" type="BigDecimal"/>
<else>
<!-- before we do the find, put together the orderBy list based on which reserveOrderEnumId is specified -->
<!-- FIFO=first in first out, so it should be order by ASCending receive or expire date
LIFO=last in first out, so it means order by DESCending receive or expire date
-->
<if-compare value="INVRO_GUNIT_COST" operator="equals" field="parameters.reserveOrderEnumId">
<set value="unitCost DESC" field="orderByString"/>
<else>
<if-compare value="INVRO_LUNIT_COST" operator="equals" field="parameters.reserveOrderEnumId">
<set value="+unitCost" field="orderByString"/>
<else>
<if-compare value="INVRO_FIFO_EXP" operator="equals" field="parameters.reserveOrderEnumId">
<set value="+expireDate" field="orderByString"/>
<else>
<if-compare value="INVRO_LIFO_EXP" operator="equals" field="parameters.reserveOrderEnumId">
<set value="-expireDate" field="orderByString"/>
<else>
<if-compare value="INVRO_LIFO_REC" operator="equals" field="parameters.reserveOrderEnumId">
<set value="-datetimeReceived" field="orderByString"/>
<else>
<!-- the default reserveOrderEnumId is INVRO_FIFO_REC, ie FIFO based on date received -->
<set value="+datetimeReceived" field="orderByString"/>
<set value="INVRO_FIFO_REC" field="parameters.reserveOrderEnumId"/>
</else>
</if-compare>
</else>
</if-compare>
</else>
</if-compare>
</else>
</if-compare>
</else>
</if-compare>
<set from-field="parameters.quantity" field="quantityNotTransferred"/>
<set field="locationTypeEnumIds" value="${groovy: ['FLT_PICKLOC', 'FLT_BULK', null]}"/>
<iterate list="locationTypeEnumIds" entry="locationTypeEnumId">
<find-by-and entity-name="InventoryItemAndLocation" map="lookupFieldMap" list="inventoryItemAndLocations" use-iterator="true" order-by-list="orderByList"/>
<entity-condition entity-name="InventoryItemAndLocation" list="inventoryItemAndLocations">
<condition-list>
<condition-expr field-name="locationTypeEnumId" value="${locationTypeEnumId}" ignore-if-empty="true" ignore-if-null="true"/>
<condition-expr field-name="productId" value="${parameters.productId}"/>
<condition-expr field-name="containerId" value="${parameters.containerId}" ignore-if-empty="true" ignore-if-null="true"/>
<condition-expr field-name="facilityId" value="${parameters.facilityId}"/>
<condition-expr field-name="availableToPromiseTotal" operator="greater" value="0"/>
</condition-list>
<order-by field-name="${orderByString}"/>
</entity-condition>
<!-- first transfer InventoryItems in FLT_PICKLOC type locations, then FLT_BULK locations, then InventoryItems with no locations -->
<iterate list="inventoryItemAndLocations" entry="inventoryItemAndLocation">
<clear-field field="inputMap"/>
<set field="inputMap.inventoryItemId" from-field="inventoryItemAndLocation.inventoryItemId"/>
<if-empty field="parameters.statusId">
<set field="inputMap.statusId" value="IXF_REQUESTED"/>
<else>
<set field="inputMap.statusId" value="${parameters.statusId}"/>
</else>
</if-empty>
<set field="inputMap.facilityId" from-field="parameters.facilityId"/>
<set field="inputMap.facilityIdTo" from-field="parameters.facilityIdTo"/>
<set field="inputMap.sendDate" from-field="parameters.sendDate"/>
<!-- TODO: inventory transfers for serialized items are not yet implemented -->
<if-compare field="inventoryItemAndLocation.inventoryItemTypeId" operator="equals" value="NON_SERIAL_INV_ITEM">
<if-compare-field field="quantityNotTransferred" to-field="inventoryItemAndLocation.availableToPromiseTotal" operator="greater" type="BigDecimal">
<set field="inputMap.xferQty" from-field="inventoryItemAndLocation.availableToPromiseTotal"/>
<else>
<set field="inputMap.xferQty" from-field="quantityNotTransferred"/>
</else>
</if-compare-field>
<add-error>
<fail-message message="inputMap = ${inputMap}"/>
</add-error>
<call-service service-name="createInventoryTransfer" in-map-name="inputMap"/>
<add-error>
<fail-message message="quantityNotTransferred = ${quantityNotTransferred}"/>
</add-error>
<calculate field="quantityNotTransferred">
<calcop operator="subtract" field="quantityNotTransferred">
<calcop operator="get" field="inputMap.xferQty"/>
</calcop>
</calculate>
</if-compare>
<if-compare operator="equals" value="0" field="quantityNotTransferred">
<break/>
</if-compare>
</iterate>
</iterate>
</else>
</if-compare>
<field-to-result field="quantityNotTransferred"/>
<if-compare operator="greater" value="0" field="quantityNotTransferred">
<add-error>
<fail-property resource="ProductUiLabels" property="FormFieldTitle_quantityNotAvailable"/>
</add-error>
<add-error>
<fail-message message="${quantityNotTransferred}"/>
</add-error>
<check-errors/>
</if-compare>
</simple-method>
<simple-method method-name="changeOwnerUponIssuance" short-description="If product store setOwnerUponIssuance is Y or empty, set the inventory item owner upon issuance.">
<entity-one entity-name="ItemIssuance" value-field="itemIssuance"/>
<get-related-one value-field="itemIssuance" relation-name="InventoryItem" to-value-field="inventoryItem"/>
<if-not-empty field="inventoryItem">
<if-compare field="inventoryItem.inventoryItemTypeId" operator="equals" value="SERIALIZED_INV_ITEM">
<get-related-one value-field="itemIssuance" relation-name="OrderHeader" to-value-field="orderHeader"/>
<if-not-empty field="orderHeader">
<set field="orderRoleAndMap.orderId" from-field="orderHeader.orderId"/>
<set field="orderRoleAndMap.roleTypeId" value="END_USER_CUSTOMER"/>
<find-by-and entity-name="OrderRole" list="orderRoles" map="orderRoleAndMap"/>
<first-from-list list="orderRoles" entry="orderRole"/>
<entity-one entity-name="ProductStore" value-field="productStore" auto-field-map="false">
<field-map field-name="productStoreId" from-field="orderHeader.productStoreId"/>
</entity-one>
<if>
<condition>
<and>
<not><if-empty field="orderRole"/></not>
<or>
<if-empty field="productStore"/>
<if-empty field="productStore.setOwnerUponIssuance"/>
<if-compare field="productStore.setOwnerUponIssuance" operator="equals" value="Y"/>
</or>
</and>
</condition>
<then>
<set field="updateContext.ownerPartyId" from-field="orderRole.partyId"/>
</then>
</if>
</if-not-empty>
<set field="updateContext.inventoryItemId" from-field="inventoryItem.inventoryItemId"/>
<call-service service-name="updateInventoryItem" in-map-name="updateContext"/>
</if-compare>
</if-not-empty>
</simple-method>
<simple-method method-name="setOrderReservationPriority" short-description="Sets priority of an order for Inventory Reservation, orders with HIGH priority would be served first.">
<set field="orderId" from-field="parameters.orderId"/>
<entity-one entity-name="OrderHeader" value-field="orderHeader">
<field-map field-name="orderId" from-field="orderId"/>
</entity-one>
<set field="priority" from-field="parameters.priority"/>
<if-empty field="priority">
<entity-and entity-name="OrderItemShipGrpInvRes" list="oisgirs">
<field-map field-name="orderId" from-field="orderId"/>
</entity-and>
<iterate list="oisgirs" entry="oisgir">
<set field="oisgir.priority" default-value="2"/>
<store-value value-field="oisgir"/>
</iterate>
<set field="orderHeader.priority" default-value="2"/>
<store-value value-field="orderHeader"/>
<else>
<set field="orderHeader.priority" from-field="priority"/>
<store-value value-field="orderHeader"/>
<entity-and entity-name="OrderItemShipGrpInvRes" list="oisgirs">
<field-map field-name="orderId" from-field="orderId"/>
</entity-and>
<iterate list="oisgirs" entry="oisgir">
<set field="oisgir.priority" from-field="priority"/>
<store-value value-field="oisgir"/>
<clear-field field="oisgir"/>
</iterate>
<entity-condition entity-name="OrderItemAndShipGrpInvResAndItem" list="oisgirais">
<condition-expr field-name="orderId" operator="equals" from-field="orderId"/>
</entity-condition>
<iterate list="oisgirais" entry="oisgir">
<set field="reassignInventoryReservationsCtx.productId" from-field="oisgir.productId"/>
<set field="reassignInventoryReservationsCtx.facilityId" from-field="oisgir.facilityId"/>
<call-service service-name="reassignInventoryReservations" in-map-name="reassignInventoryReservationsCtx"/>
<clear-field field="reassignInventoryReservationsCtx"/>
</iterate>
</else>
</if-empty>
</simple-method>
<simple-method method-name="setLastInventoryCount" short-description="Service that updates stock availability of products">
<entity-one entity-name="InventoryItem" value-field="inventoryItem" auto-field-map="false">
<field-map field-name="inventoryItemId" from-field="parameters.inventoryItemId"/>
</entity-one>
<entity-and entity-name="ProductFacility" list="productFacilities">
<field-map field-name="productId" from-field="inventoryItem.productId" />
</entity-and>
<if-not-empty field="productFacilities">
<iterate list="productFacilities" entry="productFacility">
<set field="serviceInMap.productId" from-field="productFacility.productId"/>
<set field="serviceInMap.facilityId" from-field="productFacility.facilityId"/>
<call-service service-name="getInventoryAvailableByFacility" in-map-name="serviceInMap">
<result-to-field result-name="availableToPromiseTotal"/>
</call-service>
<clear-field field="serviceInMap"/>
<set field="productFacility.lastInventoryCount" from-field="availableToPromiseTotal"/>
<set-service-fields service-name="updateProductFacility" map="productFacility" to-map="serviceInMap"/>
<call-service service-name="updateProductFacility" in-map-name="serviceInMap"/>
<clear-field field="productFacility"/>
<clear-field field="serviceInMap"/>
</iterate>
</if-not-empty>
</simple-method>
<simple-method method-name="createUpdateFacilityGeoPoint" short-description="Create or update GeoPoint assigned to facility">
<if-empty field="parameters.geoPointId">
<set-service-fields service-name="createGeoPoint" map="parameters" to-map="createGeoPointMap"/>/>
<call-service service-name="createGeoPoint" in-map-name="createGeoPointMap">
<result-to-field result-name="geoPointId" field="geoPointId"/>
</call-service>
<entity-one entity-name="Facility" value-field="facility"/>
<set field="facility.geoPointId" from-field="geoPointId"/>
<store-value value-field="facility"/>
<else>
<set-service-fields service-name="updateGeoPoint" map="parameters" to-map="updateGeoPointMap"/>/>
<call-service service-name="updateGeoPoint" in-map-name="updateGeoPointMap"/>
</else>
</if-empty>
</simple-method>
<simple-method method-name="createInventoryItemLabelAppl" short-description="Create an InventoryItemLabelAppl">
<make-value entity-name="InventoryItemLabelAppl" value-field="newEntity"/>
<set-pk-fields map="parameters" value-field="newEntity"/>
<set-nonpk-fields map="parameters" value-field="newEntity"/>
<entity-one entity-name="InventoryItemLabel" value-field="inventoryItemLabel"/>
<set field="newEntity.inventoryItemLabelTypeId" from-field="inventoryItemLabel.inventoryItemLabelTypeId"/>
<create-value value-field="newEntity"/>
</simple-method>
</simple-methods>