Add a feature to Smart Office SDK

After my previous post Hello World of Infor Smart Office SDK, I will now illustrate how to add a feature to Infor Smart Office SDK, for example the Infor Customer Lifecycle Management (CLM) feature. For that, we’ll need to get the feature files and add them to our Visual Studio Solution.

Get the feature files

To get the feature files, we need to find the Manifest and Application extension (DLL) files of our feature.

From the Smart Office server

You can find the feature files in the Infor LifeCycle Manager (LCM) server > products > components folder, and unzip the feature:

From Infor Xtreme

You can also find the feature files by downloading and unzipping the feature from Infor Xtreme. In my case I couldn’t find the same version of CLM to download as my server, but I put screenshots anyway to illustrate the point:

7.2__

From the local deployment

You can also find the feature files locally in the current ClickOnce application deployment folder of your computer:

  1. Start Smart Office from the server (not Smart Office Developer from Visual Studio).
  2. Select Show > Settings > Infor Smart Office, switch the Log level to Debug, and click Save:
    6.2
  3. Logoff Smart Office and logon again.
  4. Select the Help menu (question mark icon at the top right) > About Infor Smart Office > View log file:
    6.2
  5. Filter Origin with RegisterGroups, filter Message with the Manifest of the feature you want to add, for instance LCLM.manifest, and notice the path to the Manifest, for example C:\Users\…\AppData\Local\Apps\2.0\Data\…\…\http…\Data\F\<feature>\ :
    6.4_
  6. And open that path in Windows Explorer:
    3.2___

Reference the files in Visual Studio

Once you found the Manifest and Application extension (DLL) files:

  1. Copy/paste them to the Bin directory of your SDK root directory:
    3.2__
  2. Open your Visual Studio solution, select the MangoClient Project, and select PROJECT > Add Reference:
    3.1_
  3. Browse to the Bin directory, select the DLL files you just pasted, and click OK:
    6.7
  4. Now Rebuild your solution, Start it, and the feature will be there in your Smart Office Developer mode:
    3.9

That’s it!

Note: Instead of copy/pasting the files to the Bin directory, I also tried adding a reference to the files of the ClickOnce directory, i.e. no need to copy/paste, and it worked fine. But the Developer’s Guide states “The feature assemblies and the feature manifest must be copied to the bin directory so that the framework can load the feature when the client starts.” Maybe there’s a reason I don’t know about, so I followed their instructions.

If you liked this post, subscribe to this blog and we’ll send you an email notification when we write a new post. Also, become a contributor and post your M3 ideas.

Hello World of Infor Smart Office SDK

Today I will write a post on how to start with the Infor Smart Office SDK as a continuation of Karin’s getting started post and Scott’s one to eight part series. We use the Smart Office SDK to develop applications in C#/WPF that run inside Smart Office with the benefits of look & feel, session, user information, M3 context, integration to M3 APIs and M3 Web Services, etc. I’m developing an application for a customer to integrate Cisco IP phones with Infor Customer Lifecycle Management (CLM). This is a great opportunity for me to share with you interesting bits and pieces as I progress, and it’s a way for me to anchor what I learn. I will put screenshots galore.

Here are the steps:

  1. Download and install Microsoft Visual Studio for C# and WPF development. I opted for the free Visual Studio Express 2013 for Windows Desktop:
    1.1
  2. Launch it from the Windows Start menu > All Programs > Visual Studio 2013 > VS Express 2013 for Desktop:
    1.2
  3. Download and install the Infor Smart Office SDK from Infor Xtreme > Downloads > ProductsUser Productivity PlatformSmart Office SDK – M3; choose the same version as the target Smart Office server you will develop for, version 10.1 in my case:
    2.1
  4. Unzip to a temporary folder in your computer and move the contents to the SDK root directory, for example C:\InforSmartOfficeSDK\ ; I moved to a folder with the full version number as I might have to switch back and forth between different versions for customer projects:
    2.2
  5. For more information , read the installation instructions in the Infor Smart Office Developers Guide at path Documentation\DevelopersGuide.pdf:
    0
  6. Set an environment variable LSOSDKBin in My Computer > Properties > Advanced system settings > Environment Variables, to the path of the Bin sub-folder, in my case it’s C:\InforSmartOfficeSDK_10.1.1.1_20130920\Bin :
    2.4
  7. Edit the contents of the file RegisterServer.reg in a text editor like Notepad, and change the value of Server to point to the scheme://domain:port of your Smart Office server URL. Then execute the file to merge the new MangoDev Key into your Windows Registry (C:\Windows\regedit.exe); in my case I later manually added String values for Username and Password so I don’t get authentication prompts anymore (beware of this security risk):
    2.5_
  8. Open the Sample Solution Samples\Samples.sln in Visual Studio:
    2.7
  9. Select BUILD > Build Solution F7:
    2.8
  10. Select DEBUG > Start Debugging F5:
    2.9
  11. That will start Smart Office in Developer mode. Select Help (question mark icon at the top right) > About Infor Smart OfficeView features, that will show you the Framework Examples and the Sample Feature:
    2.10_
  12. Start the Hello World application in the Sample menu of the Navigator widget:
    2.12
  13. And try the other samples and widgets:
    2.11

That’s it!

If you like this, subscribe to this blog to receive an email notification when we write posts, and become a contributor and write your M3 ideas.

Hacking Customer Lifecycle Management (CLM)

Today I will show you how I made simple modifications to Infor Customer LifeCycle Management (CLM), the CRM product for Infor M3. With CLM standard out-of-the-box we only have the ability to show/hide fields, for example choosing whether or not we want the name, address, and phone number columns in the list or the fields in the details view. CLM is a great product, and by design it is intended to be simple to use. In my case I wanted something more: I needed to add a call button next to the phone number. That is not officially possible by default so I had to do some hacking.

I’m working on a project for a customer to integrate Cisco IP phones with CLM, such that when a customer service representative on the phone receives an incoming phone call we automatically pop-up the corresponding customer data on the screen, and conversely, such that they can click a phone number in CLM and make that outgoing phone call. I had already done some work in the past integrating Skype with Smart Office, and integrating ShoreTel phones with Smart Office. This time it’s Cisco IP phones. I cannot show you the entire source code as it’s propriety of the customer and my employer, but I will show you interesting bits and pieces, and the writing helps me clean-up my code too.

I will show you:

  • How to get the list of open CLM windows
  • How to find the phone number field
  • How to add a button to CLM and use the Design System Icons
  • How to make the outgoing phone call

About CLM

To tell if you have CLM, go to Smart Office > Help > About Infor Smart Office > View features, and you will see CLM Application:
1

Then, go to the Navigator widget, you will see the menu Customer Lifecycle Management, expand it and launch My Accounts > All:
2

It will open the list of accounts:
3_

Double-click one of the rows to open the account details:
4_

How to get the list of open CLM windows

Now we have two CLM windows open: the list of accounts, and the details of an account. To programmatically get that list, I use the DashboardTaskService.FindRunningTaskByUri method, to discriminate by Uri lclm://
0


var list /*System.Collections.Generic.List<Mango.UI.Services.FindTaskResult>*/ = DashboardTaskService.Manager.FindRunningTaskByUri("lclm://", TaskMatch.StartsWith);
for (var result : FindTaskResult in list) {
    var runner : IRunner = result.Runner;
    var task : ITask = runner.Task;
    debug.WriteLine(task.Uri);
}

That will return two tasks:
lclm://filter/?ActionType=View&MainTableID=…&FilterGroupID=…&SubFilterID=…
lclm://details/?ActionType=View&MainTableID=…&PrimaryKey=…

Now we need to tell apart the Accounts windows from the other potential CLM windows such as Activities or Contacts:

var host : IInstanceHost = runner.Host;
if (host.HostTitle.StartsWith("Account")) {
    // ...
}

This code will only work for English. Ideally we would use an official CLM API that returns the correct Tasks, but I haven’t found one. Let me know if you find one.

How to find the phone number field

Now that we have the correct window, we can get its contents and find the phone number field. First, I use WPF Inspector to visually introspect the window and find the phone number field in the visual tree:
6

The fields are layed out in one of the ancestor Grids:
5

More specifically the phone number field is itself a Grid of one row and three columns:
6_

That’s where I’ll inject my button. To get there programmatically, I use the VisualTreeHelper, and I do a pre-order depth first search:

function ... {
    //...
    var content : FrameworkElement = host.HostContent;
    if (content.GetType().ToString() == "lclmControls.Custom.TabularDetailsView") {
        var o: DependencyObject = FindPhoneTextBox(content);
        if (o != null) {
            var txtbox: SingleLineTextBox = o;
        }
    }
}
function FindPhoneTextBox(o : DependencyObject): DependencyObject {
    // visit node
    if (o != null) {
        if (o.GetType().ToString().EndsWith("SingleLineTextBox")) {
            var txtbox: SingleLineTextBox = o;
            if (txtbox.Name == "Phone") {
                return o;
            }
        }
    }
    // visit children
    for (var i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++) {
        var child = VisualTreeHelper.GetChild(o, i);
        var result: DependencyObject = FindPhoneTextBox(child);
        if (result != null) {
            return result;
        }
    }
    // not found
    return null;
}

How to add a button to CLM and use the Design System Icons

Now that we found the phone number field and the Grid, we can add a button. I will use the IconButtons of the Design System as illustrated by norpe:

var btn: IconButton = new IconButton();
btn.IconName = "Phone";
btn.ToolTip = "Call this phone number."
btn.HorizontalAlignment = HorizontalAlignment.Left;
btn.Margin = new Thickness(5, 0, 0, 0);
btn.Tag = txtbox; // remember the textbox
Grid.SetRow(btn, 0);
Grid.SetColumn(btn, 2);
Grid.SetColumnSpan(btn, 2);
var grid: Grid = VisualTreeHelper.GetParent(txtbox);
grid.Children.Add(btn);
btn.add_Click(OnCall);

This is the result, with the IconButton hover and ToolTip:
7_

Note: to get the Grid I used the textboxe’s parent. This assumption is true in CLM version 1.0.0.99, but could be false in a future version of CLM in which case this code would break. Ideally we would have an official CLM API for this.

How to make the outgoing phone call

Now you can do whatever with the phone number, for example use the default operating system’s URI handler for the tel scheme which is Skype in my case.

function OnCall(sender: Object, e: RoutedEventArgs) {
    try {
        var btn: Button = sender;
        var txtbox: SingleLineTextBox = btn.Tag;
        var phoneNumber: String = txtbox.Text;
        var uri: Uri = new Uri("tel:" + phoneNumber); // RFC 3966
        ScriptUtil.Launch(uri);
    } catch (ex : Exception) {
        debug.WriteLine(ex);
    }
}

And here’s the result:
8_

The method ScriptUtil.Launch will instruct the operating system to execute the specified command. That’s the equivalent of typing start command at the DOS prompt. In our case it’s:

start tel:+14156247033

9

That means any special characters of the command must be escaped, such as white spaces and ampersands. To escape white spaces in DOS that means enclosing the entire string in double-quotes. I tried enclosing the URI in double quotes, and it didn’t work. I also tried other escaping and encoding techniques like using backslash, plus sign, and %20, and they didn’t work either. So let’s simply strip it out:

phoneNumber = phoneNumber.Replace(' ', '');

Also, in my example I used a phone number that’s already correctly formatted in international E.123 notation which Skype understands. To validate the phone number, we can use a regular expression. A simple one is to strip all characters and keep only the plus sign and the digits, but that’s probably not fully compliant with the E.123 specification so we need to work more on this in the future:

var regex = /[^\+^\d]/g;
phoneNumber =  phoneNumber.replace(regex, "");

Future work

Future work includes:

  • Use an event handler to listen for new CLM windows to automatically add the Call button as the user opens the Account views. I couldn’t find an event, and Karin confirmed it’s not currently supported. I tried MForms.MainController, DashboardTaskService, DashboardTaskBar, lclmControls.EventNotifier, WindowManager, etc. I found an event handler for M3 Forms, an event handler for the Quick Start CTRL+R, and private event handlers that would have worked had they been public. Nothing I could use. I ended up using a worker that’s polling Tasks every second in a background thread (yikes).
  • Remember we added the button so we don’t add it again next time.
  • Add the Call button on all phone number fields: fax, mobile phone, home phone, etc.
  • Validate the phone number with a regular expression that complies with the specifications.
  • Make the outgoing call thru the Cisco IP phone instead of using Skype.
  • Listen for incoming phone calls.
  • Move the script to a widget using the Smart Office SDK.

That’s it! If you like this post, subscribe to this blog. And if you rock, become an author to share your ideas.

Open source release of Address Validation for M3

I’m please to announce I finally released the first free and open source version of the Address Validation for M3, and I published the code on GitHub at https://github.com/M3OpenSource/AddressValidation . It’s a combination of a Mashup and a Script for Infor Smart Office, so it’s easy for you to modify, deploy, and run. Currently, it only supports the Google Geocoding API with more address providers to be added later.

I had implemented the first proprietary version of the script for Lawson Software in 2009. Then I had implemented several variants for various customers while working at Lawson Software and Infor. I’ve always wanted it to become available to more customers, so I decided to make it free and open source. But in order to not infringe any intellectual property and copyrights over the previous source code, I had to re-write everything from scratch. The opportunity came up when I quit Infor, and before I joined Ciber, in between the two, so there was no question on the ownership of the source code, and I had made an announcement. It took me a while but here it is. And I made some improvements over to the previous proprietary code.

Self-configuration

The script is self-configurable for the following M3 Panels, i.e. just add the script to one of the supported M3 Panels and execute, without any modifications nor arguments:

  • Customer. Open – CRS610/E
  • Customer. Connect Addresses – OIS002/E
  • Supplier. Connect Address – CRS622/E
  • Customer Order. Connect Address – OIS102/E
  • Internal Address. Open – CRS235/E1
  • Company. Connect Division – MNS100/E
  • Ship-Via Address. Open – CRS300/E
  • Service Order. Connect Delivery Address – SOS005/E
  • Shop. Open – OPS500/I
  • Bank. Open – CRS690/E
  • Bank. Connect Bank Branch Office – CRS691/E
  • Equipment Address. Open – MOS272/E

Deploy locally and test

To start using the script, download the Mashup’s Manifest and XAML and the Script from the GitHub repository. Save the three files somewhere temporary on your computer. Then, install the Mashup locally using the Mashup Designer at designer://mashup (watch the video below), and run the Script with the Script Tool at mforms://jscript (watch the video below). Then, enter an address in M3, click the validate button (the little globe icon), you can also use the TAB key from Address line 1 to focus the button and press SPACE to click it, then the Mashup will pop-up with a list of possible matches, and select an address by pressing ENTER or double-clicking the address.

Screenshots

Here are some screenshots:

1 2 3 4 5

Videos

Here are some videos (watch in full-screen and high-definition for better view):

  • How to deploy the Mashup locally
  • How to test the Script
  • Sample address searches:

Then, you can deploy the Mashup globally with LifeCycle Manager, and set the script to everybody with the Smart Office Personalization Manager in the Navigator widget > Administration tools.

Future work

There’s still more work to do. For instance, it appears the Google Geocoding API doesn’t follow the same format for all addresses, they’re local to each country, so right now we have to manually change the address layout based on the country, and I would like to improve that.

Also, I want to add a WebBrowser control to show the addresses in Google Maps.

Also, this first release only supports the Google Geocoding API. I want to add support for other address providers, like Experian QAS, FedEx, Microsoft Bing Maps, UPS, United States Postal Service, and local address providers like Pages Jaunes in France, and Eniro in Sweden.

If you like it, join the project and become a contributor!

Thibaud Lopez Schneider

Mashup – SQL Wildcard / DataGrid with sorting / Copy to Clipboard

Hi,

It’s been a while since I shared something now… I primary spent my time working with mashup combined with SQL… and when I first started there was no turning back.     The same about the DataGrid, its tooo powerful!

Today i’ll share one of probably many ways, how you can perform a wildcard search from a mashup.  I’ll also drop you a few tips when using the DataGrid regarding copying and sorting.

– SQL – WildCard

This solution I provide here now, should give pretty much the exact same results as searching with standard M3.

The use provides an input to a TextBox.

You need to add the following to your mashup to be able to use the IsNotNullOrEmptyConverter

xmlns:utils=”clr-namespace:Mango.UI.Utils;assembly=Mango.UI”

We then use a DataTrigger to provide us a default value if the TextBox is blank, in this case the % to be used in the SQL LIKE(‘%’)  NB: This is a hidden TextBox

<TextBox Name="ItemWhsCheck" Visibility="Hidden">
     			  <TextBox.Style>
      			 <Style x:Key="ItemWhsCheck" TargetType="{x:Type TextBox}" BasedOn="{StaticResource {x:Type TextBox}}">
         			 <Setter Property="Text" Value="{Binding ElementName=ItemWhs, Path=SelectedValue}" />
	        			  <Style.Triggers>
	            		 	<MultiDataTrigger>
	               		 	<MultiDataTrigger.Conditions>
	                   			<Condition Binding="{Binding ElementName=ItemWhs, Path=SelectedValue, Converter={StaticResource IsNotNullOrEmptyConverter}}" Value="False" />
	                			</MultiDataTrigger.Conditions>
	                			<Setter Property="Text" Value="%" />
	            			</MultiDataTrigger>
         		 		</Style.Triggers>
      				 </Style>
    				</TextBox.Style>
  </TextBox>

We should then be able to bind the correct values to the provide it to the SQL.

	<mashup:Parameter TargetKey="ListItems1.MLWHLO" Value="{Binding ElementName=ItemWhsCheck, Path=Text}" />

In the LWS Designer the “?” will be the Input from the mashup.

SELECT MMITNO,MMFUDS from MITMAS  WHERE MMCONO=1  AND TRIM(MMFUDS) LIKE REPLACE(CAST( ? AS VARCHAR(1000)), ‘*’, ‘%’)

This will perform a search which allows exact hits, or multiple hits and the use of multiple wildcards.

Search: O-RING*  Search: O-RING, BS-204 VITON 90  Search- O-RING*204*90

All will provide the following result.  Result: O-RING, BS-204 VITON 90

You can also combine multiple of these.

AND TRIM(MMFUDS) LIKE REPLACE(CAST( ? AS VARCHAR(1000)), ‘*’, ‘%’)  AND TRIM(MMITNO) LIKE REPLACE(CAST( ? AS VARCHAR(1000)), ‘*’, ‘%’)  AND MMITTY LIKE(CAST( ? AS VARCHAR(1000)))  AND TRIM(LISERN) LIKE REPLACE(CAST( ? AS VARCHAR(1000)), ‘*’, ‘%’)  AND MLWHLO LIKE(CAST( ? AS VARCHAR(1000)))  AND TRIM(MLWHSL) LIKE REPLACE(CAST( ? AS VARCHAR(1000)), ‘*’, ‘%’)  AND MLCAMU LIKE(CAST( ? AS VARCHAR(1000)))

DataGrid – Copy to Clipboard

In this example I’m adding a ContextMenu to the DataGrid to allow to copy the line infomration to mail/excel, but you could just aswell use a button .  I added th following extra properties:

The SelectionMode is set to Exteded to allow multiple selections .  The ClipBoardCopyMode is set to IncludeHeaders

<DataGrid Name="dataGrid1" ItemsSource="{Binding ResultSetCollection}" Style="{DynamicResource styleDataGrid}" SelectionMode="Extended" ClipboardCopyMode="IncludeHeader" AutoGenerateColumns="False" SelectedIndex="0" Visibility="{Binding ElementName=SerialRadio, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}">
			<DataGrid.ContextMenu>
				<ContextMenu>
					<MenuItem Header="Copy to clipboard" Command="ApplicationCommands.Copy" CommandTarget="{Binding ElementName=dataGrid1}" />
				</ContextMenu>
			</DataGrid.ContextMenu>

You should now be able to use the Copy to Clipboard, if using the standard DataGridTextColumns

However, if you would like to use the DataGridTemplateColumn you need to add ClipboardContentBinding or you will be copying blank values.

When using the DataGridTempalte, you also will be losing the standard sorting options, unless you add CanUserSort and SortMemberPath.

The function of this TemplateColumn is just to colour the text red if the overdue value = 1


<DataGridTemplateColumn Header="Next test date" CanUserSort="True" SortMemberPath="NEXTDATE" ClipboardContentBinding="{Binding NEXTDATE}">
					<DataGridTemplateColumn.CellTemplate>
						<DataTemplate>
							<StackPanel Orientation="Horizontal">
								<TextBlock MinWidth="80" MaxWidth="0" Text="{Binding NEXTDATE}">
									<TextBlock.Style>
										<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}">
											<Style.Triggers>
												<MultiDataTrigger>
													<MultiDataTrigger.Conditions>
														<Condition Binding="{Binding Overdue}" Value="1" />
													</MultiDataTrigger.Conditions>
													<Setter Property="Background" Value="Red" />
													<Setter Property="Text" Value="{Binding NEXTDATE}" />
												</MultiDataTrigger>
											</Style.Triggers>
										</Style>
									</TextBlock.Style>
								</TextBlock>
							</StackPanel>
						</DataTemplate>
					</DataGridTemplateColumn.CellTemplate>
				</DataGridTemplateColumn>
<DataGridTextColumn Header="Service" Binding="{Binding NEXTSERVICE}" />

This is not the full .xaml ….  But it should contain alot of ideas  I hope its worth sharing.

Here is an example of an end result, and the speed is amazing.

ItemSearch

Please take contact if you got any questions, or are willing to share some of your own ideas.
And I also need to thank Heiko for providing solutions when everything looks dark! 🙂

And since I haven’t seen many M3 SQL’s out there…. Here is a copy of the one behind this mashup, i’m no proffessional, so there might be minor errors.

select MMCONO,MMITNO,MMFUDS,MMITTY,LISERN,MLWHLO,MLWHSL,MLCAMU,MMITGR,LISTAT,
(Select A.MPPOPN from MITPOP A where A.MPCONO=1 and A.MPALWT=’4′ AND A.MPITNO=MMITNO fetch first 1 row only) as Partno,
(Select Case When Count(mlbano) > ‘0’ Then ‘*’ Else ” end from mitloc where mlcono=1 and mlcamu=lisern) as CONTAINED,
(Select Case When Count(asnhsn) > ‘0’ Then ‘*’ Else ” end from MROABS where ascono=1 and (asitno=mmitno and assern=lisern) OR (asnhai=mmitno and asnhsn=lisern )) as ATTACHED,

CASE
WHEN QOSTDT is null Then
CASE
WHEN HP.QHSTDT is null THEN NULL
ELSE HP.QHSTDT
END
ELSE
CASE
WHEN HP.QHSTDT is null THEN QOSTDT
ELSE
CASE
WHEN QOSTDT HP.QHSTDT Then HP.QHSTDT
END
END
END as NEXTDATE,

CASE
WHEN QOSTDT is null Then
CASE
WHEN HP.QHSTDT is null THEN NULL
ELSE HP.QHSUFI
END
ELSE
CASE
WHEN HP.QHSTDT is null THEN QOSUFI
ELSE
CASE
WHEN QOSTDT HP.QHSTDT Then HP.QHSUFI
END
END
END as NEXTSERVICE,

CASE
WHEN (days(current date ) > days (to_date(char(QOSTDT),’yyyymmdd’)))
THEN ‘1’
WHEN (days(current date ) > days (to_date(char(QHSTDT),’yyyymmdd’)))
THEN ‘1’
END AS “Overdue”,

(SELECT
CASE WHEN C.MLCAMU ” THEN C.MLWHLO
ELSE C.MLWHLO
END
FROM MITLOC C WHERE C.MLCONO=1 AND A.MLWHLO IN(‘191′,’291’) AND A.MLCAMU=C.MLBANO ) as ContWhs,

(SELECT
CASE WHEN B.MLCAMU ” THEN B.MLCAMU
ELSE B.MLWHSL
END
FROM MITLOC B WHERE B.MLCONO=1 AND A.MLWHLO IN(‘191′,’291′) AND A.MLCAMU=B.MLBANO ) as ContLoc

FROM
MITMAS
JOIN MILOIN ON LICONO=1 AND LIITNO=MMITNO
JOIN MITLOC A ON MLCONO=1 and LIITNO=MLITNO and LISERN=MLBANO
LEFT JOIN MWOPLP ON QOCONO = MLCONO AND QOFACI=’100’ AND QOPRNO = MLITNO AND QOBANO = MLBANO AND QOORTY = ‘SMA’ AND QOPSTS < 59
AND QOSTDT =(SELECT MIN(MP2.QOSTDT) FROM MWOPLP MP2 WHERE MP2.QOCONO = MLCONO AND MP2.QOFACI='100' AND MP2.QOPRNO = MLITNO AND MP2.QOBANO = MLBANO AND MP2.QOORTY = 'SMA' AND MP2.QOPSTS < 59)
LEFT JOIN MMOHED HP ON HP.QHCONO = MLCONO AND QHFACI='100' AND HP.QHPRNO = MLITNO AND HP.QHBANO = MLBANO AND HP.QHORTY = 'SMA' AND HP.QHWHST < 90
AND HP.QHSTDT =(SELECT MIN(HP2.QHSTDT) FROM MMOHED HP2 WHERE HP2.QHCONO = MMCONO AND HP2.QHFACI='100' AND HP2.QHPRNO = MLITNO AND HP2.QHBANO = MLBANO AND HP2.QHWHST < 90)

WHERE MMCONO=1
AND TRIM(MMFUDS) LIKE REPLACE(CAST( ? AS VARCHAR(1000)), '*', '%')
AND TRIM(MMITNO) LIKE REPLACE(CAST( ? AS VARCHAR(1000)), '*', '%')
AND MMITTY LIKE(CAST( ? AS VARCHAR(1000)))
AND TRIM(LISERN) LIKE REPLACE(CAST( ? AS VARCHAR(1000)), '*', '%')
AND MLWHLO LIKE(CAST( ? AS VARCHAR(1000)))
AND TRIM(MLWHSL) LIKE REPLACE(CAST( ? AS VARCHAR(1000)), '*', '%')
AND MLCAMU LIKE(CAST( ? AS VARCHAR(1000)))

Fetch first 500 rows only

Thanks.

Regards

Ken Eric