## Rows to columns for price breaks

No doubt your sales team want to go on the road with a human friendly version of your prices for the customers to read. It is possible to do this with a SQL table function to extract the prices from GP with price breaks. The following example assumes you know how many price breaks you have in your price lists and will result in output something like the following screen shot. These results may be squirted into excel with more columns as described by your business requirements.

Two key SQL server functions that many people I find are not familiar with but are vital for this kind of data manipulation are; “ROW_NUMBER()” and “PARTITION BY” , one way to learn is to dive in with an example.

## GP Price Table

Natively the prices are held in the table IV00108 of your company database.

 ITEMNMBR CURNCYID PRCLEVEL UOFM TOQTY FROMQTY UOMPRICE WIRE100 Z-US\$ RETAIL Foot 100 0.01 0.35 WIRE100 Z-US\$ RETAIL Foot 999999999999.99 100.01 0.29 WIRE100 Z-US\$ RETAIL Spool 999999999999.99 0.01 190 WIRE100 Z-US\$ RETAIL Yard 999999999999.99 0.01 0.65 WIRE100 Z-US\$ EXTPRCLVL Foot 999999999999.99 0.01 0 WIRE100 Z-US\$ EXTPRCLVL Yard 999999999999.99 0.01 0 WIRE100 Z-US\$ EXTPRCLVL Spool 999999999999.99 0.01 0

There is a row per “price point”. Each row contains, the item sku, Currency of the price list, price list name, unit of measure, quantity break range and price.

This is unreadable to humans once you get, say 15,000 products, five currencies and ten or so price levels. From experience, one company this solution is used with has 1,623,586 rows in the price table IV00108.

## Table Partitioning

Firstly the rows are grouped together by the common factor each output row should be sharing. Each row in this example should have the same Item, Currency, Price Level and unit of measure. A row number is added for each successive row within this grouping;

`SELECT ITEMNMBR,CURNCYID, PRCLEVEL, UOMPRICE, FROMQTY, UOFM, TOQTY, ROW_NUMBER()         OVER(PARTITION BY             ITEMNMBR,PRCLEVEL, CURNCYID, UOFM             ORDER BY toqty ASC) AS 'RowNumber'FROM iv00108 (NOLOCK) WHERE itemnmbr='WIRE100'`

The above TSQL partitions the returned rows from IV00108 by ITEMNMBR,PRCLEVEL, CURNCYID, UOFM, for each row in the group a row number is generated by ROW_NUMBER() see the following output example. For this example, there are two quanity break columns for the prices of the “foot” unit of measure.
These are breaks of; 0.01+ and 100+, resulting in row numbers one and two for this unit of measure.

 ITEMNMBR CURNCYID PRCLEVEL UOMPRICE FROMQTY UOFM TOQTY RowNumber WIRE100 Z-US\$ EXTPRCLVL 0.00000 0.01000 Foot 999999999999.99 1 WIRE100 Z-US\$ EXTPRCLVL 0.00000 0.01000 Spool 999999999999.99 1 WIRE100 Z-US\$ EXTPRCLVL 0.00000 0.01000 Yard 999999999999.99 1 WIRE100 Z-US\$ RETAIL 0.35000 0.01000 Foot 100 1 WIRE100 Z-US\$ RETAIL 0.29000 100.01000 Foot 999999999999.99 2 WIRE100 Z-US\$ RETAIL 190.00000 0.01000 Spool 999999999999.99 1 WIRE100 Z-US\$ RETAIL 0.65000 0.01000 Yard 999999999999.99 1

Now that we have the RowNumber, this can act as the anchor field to crosstab the data with. It makes sense to wrap this query in a common table expression (CTE) to clean it up. The output from the below should be identical to that above.

`WITH PriceTable (ITEMNMBR, CURNCYID, PRCLEVEL, UOMPRICE, FROMQTY,UOFM, TOQTY,[RowNumber]) AS (SELECT ITEMNMBR,CURNCYID, PRCLEVEL, UOMPRICE, FROMQTY, UOFM, toqty, ROW_NUMBER()         OVER(PARTITION BY         ITEMNMBR,PRCLEVEL, CURNCYID,UOFM         ORDER BY TOQTY ASC) AS 'RowNumber'    FROM iv00108 (NOLOCK) where itemnmbr='WIRE100' )SELECT * FROM PriceTable`

## Crosstabbing the Common Table Expression

Now building on the select statement from the CTE, it is crosstabbed by using CASE statements as shown below. All that has changed between these two scripts is the select out of the CTE. The select is also add “+” to the price from column results as well as some NULL handling to make the presentation cleaner for Excel should it end up there. This is optional, it might be more appropriate for other uses to keep the results as numeric values and do that kind of processing in the reporting tool.

`WITH PriceTable (ITEMNMBR, CURNCYID, PRCLEVEL, UOMPRICE, FROMQTY,UOFM, TOQTY,[RowNumber]) AS (SELECT ITEMNMBR,CURNCYID, PRCLEVEL, UOMPRICE, FROMQTY, UOFM, toqty, ROW_NUMBER()         OVER(PARTITION BY         ITEMNMBR,PRCLEVEL, CURNCYID,UOFM         ORDER BY TOQTY ASC) AS 'RowNumber'    FROM iv00108 (NOLOCK) where itemnmbr='WIRE100' )select itemnmbr,   isnull(max(case when PriceTable.[RowNumber] = 1 then    LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break1, max(case when PriceTable.[RowNumber] = 1 then     uomprice end) as Price1, isnull(max(case when PriceTable.[RowNumber] = 2 then    LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break2, max(case when PriceTable.[RowNumber] = 2 then     uomprice end) as Price2,isnull( max(case when PriceTable.[RowNumber] = 3 then    LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break3, max(case when PriceTable.[RowNumber] = 3 then     uomprice end) as Price3,isnull( max(case when PriceTable.[RowNumber] = 4 then    LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break4, max(case when PriceTable.[RowNumber] = 4 then    uomprice end) as Price4, isnull(max(case when PriceTable.[RowNumber] = 5 then    LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break5, max(case when PriceTable.[RowNumber] = 5 then    uomprice end) as Price5, isnull(max(case when PriceTable.[RowNumber] = 6 then     LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break6, max(case when PriceTable.[RowNumber] = 6 then     uomprice end) as Price6from PriceTablegroup by itemnmbr, curncyid, prclevel, UOFM;`

The above TSQL generates the following table, where the rows have been transformed into columns by TSQL, just as required.
 itemnmbr Break1 Price1 Break2 Price2 Break3 Price3 Break4 Price4 Break5 Price5 Break6 Price6 WIRE100 0+ 0.00000 NULL NULL NULL NULL NULL WIRE100 0+ 0.00000 NULL NULL NULL NULL NULL WIRE100 0+ 0.00000 NULL NULL NULL NULL NULL WIRE100 0+ 0.35000 100+ 0.29000 NULL NULL NULL NULL WIRE100 0+ 190.00000 NULL NULL NULL NULL NULL WIRE100 0+ 0.65000 NULL NULL NULL NULL NULL

## Table valued function

Great there we have it, price table partitioned and crosstabbed. Lets not stop there as this is much more useful as a table valued function. This is achieved by wrapping the above SQL as shown below. Here we have decided that the calling script should provide the currency, pricelist, item pattern and unit of measure to export. Your application may differ and not require the expensive type conversions.

`CREATE function [dbo].[Extract_PricesCrosstabTable] ( @CURNCYID varchar(15), @PRCLEVEL varchar(11), @ITEMPATTERN  nvarchar(31) = '%', @UOFM varchar(9) = '%')RETURNS @retTable TABLE (    [ITEMNMBR] [varchar](31) primary key NOT NULL,    [BREAK1] [varchar](255) NOT NULL,    [PRICE1] [numeric](19, 5) NULL,    [BREAK2] [varchar](255) NOT NULL,    [PRICE2] [numeric](19, 5) NULL,    [BREAK3] [varchar](255) NOT NULL,    [PRICE3] [numeric](19, 5) NULL,    [BREAK4] [varchar](255) NOT NULL,    [PRICE4] [numeric](19, 5) NULL,    [BREAK5] [varchar](255) NOT NULL,    [PRICE5] [numeric](19, 5) NULL,    [BREAK6] [varchar](255) NOT NULL,    [PRICE6] [numeric](19, 5) NULL)ASBEGINWITH PriceTable (ITEMNMBR, CURNCYID, PRCLEVEL, UOMPRICE, FROMQTY,UOFM, TOQTY,[RowNumber]) AS (SELECT ITEMNMBR,CURNCYID, PRCLEVEL, UOMPRICE, FROMQTY, UOFM, toqty, ROW_NUMBER()         OVER(PARTITION BY         ITEMNMBR,PRCLEVEL, CURNCYID,UOFM         ORDER BY TOQTY ASC) AS 'RowNumber'    FROM iv00108 (NOLOCK) where itemnmbr like @ITEMPATTERN and PRCLEVEL= @PRCLEVEL     AND CURNCYID=@CURNCYID AND UOFM LIKE @UOFM)INSERT @retTableselect itemnmbr,   isnull(max(case when PriceTable.[RowNumber] = 1 then    LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break1, max(case when PriceTable.[RowNumber] = 1 then     uomprice end) as Price1, isnull(max(case when PriceTable.[RowNumber] = 2 then    LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break2, max(case when PriceTable.[RowNumber] = 2 then     uomprice end) as Price2,isnull( max(case when PriceTable.[RowNumber] = 3 then    LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break3, max(case when PriceTable.[RowNumber] = 3 then     uomprice end) as Price3,isnull( max(case when PriceTable.[RowNumber] = 4 then    LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break4, max(case when PriceTable.[RowNumber] = 4 then    uomprice end) as Price4, isnull(max(case when PriceTable.[RowNumber] = 5 then    LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break5, max(case when PriceTable.[RowNumber] = 5 then    uomprice end) as Price5, isnull(max(case when PriceTable.[RowNumber] = 6 then     LTRIM( STR(FROMQTY,6,0)) + '+' end),'') as Break6, max(case when PriceTable.[RowNumber] = 6 then     uomprice end) as Price6from PriceTablegroup by itemnmbr, curncyid, prclevel, UOFM;RETURNEND;`

## Putting it to work

Now it is a table valued function, this allows a crosstabbed price table to be used as if it were a table. For example to add in the item description from the item master table IV00101, the following is used;

`SELECT IV00101.ITEMDESC, PricesCrossTab.* From Extract_PricesCrosstabTable('Z-US\$','RETAIL','WIRE%','Foot') PricesCrossTabJOIN IV00101ON PricesCrossTab.ITEMNMBR= IV00101.ITEMNMBR`

The unit of measure has been used as a parameter here for selection, however by changing the schema of the table valued function returned table type to include unit of measure as part of the primary key, all units of measure can be returned. This is the foundations of some scripts that can be amended to produce the results that you require for your particular circumstances.

The regulator is a great tool for building and testing regular expressions that I have been using for many years now. I know I should move on (see end of post), however it is familiar and does what I need.

## Problem starting TheRegulator

Sometimes when it is not closed correctly it can stop working on start up. The form Ctor fails. The exception is something like, “

"Application has generated an exception that could not be handled.”

If this happens navigate to the application directory and remove the settings file in the application directory and all will be well again. A new settings file will be created.

This is a common problem I have with settings files myself. They can cause the application to fail at start up if they get corrupted, that is surprisingly common with workers wanting to get home quicker by turning off the PC before it has finished shutdown.

http://sourceforge.net/projects/regulator/

The developer community seem to like http://www.regexbuddy.com/

This is a paid for app unlike The Regulator. RegexBuddy gets good things said about it though.

Mergers and acquisitions present new challenges in all parts of the business and in my most recent challenge it has presented the opportunity to solve the problem of allowing one GP company with inventory to resell a recently acquired GP company's stock. Both companies now members of the same holding group. As it was suspected that there would be a rapid high volume uptake of the new products, the system had to run itself.

For sound strategic, legal and financial reasons the GP companies have to be maintained as separate entities, they may be hosted on different servers in different physical locations.

## How it was done

The main requirements are;

• Item enquiry and SOP entry staff should have sight of the stock held in the other company.
• The selling price in the reseller company is totally independent of the originating company selling prices.
• The cost price in the reseller company should be the FIFO cost price from the first company.
• Stock must be reserved (allocated) as soon as possible to the reseller company to guarantee stock promises to customers.

Resulting from a couple of meetings with the stakeholders and a white boarding session, we finally picked the the best fit solution from the various proposed ideas and thus crafting of the system began.

The core of the solution is to utilise stock adjustment out of the source company and a matching stock adjustments into the reseller company. Ensuring the FIFO cost from the out adjustment of the source company is used as the cost to adjust into the selling company with. This stock requisition system had to be automatic to deal with the potential volumes of transactions each day. Easy was to get eConnect to create the inventory transactions, but more challenging was the issue of how to get those inventory transactions posted automatically.

## Automatic posting of Dynamic GP transactions

Automatic posting of transactions is an integration issue often faced. There is no API exposed to allow developers to post transactions in GP. This lack of a posting API is an unfortunate gap in the developer tools experience.

Recently discovered was the Post Master product by Envisage Software. this software runs as a .NET plug-in to Dynamics GP, allowing automated posting of batches. It can be controlled through a control table, providing the long sought after hook into the posting process. Post Master will be covered in another blog post, it is a well written robust solution for automatic posting of transaction batches in Microsoft Dynamics GP.

## Controlling the requisition

A set of transaction tables have been created for controlling the process of requisitioning stock. These tables allow stock to be requested and track progress as the process creates the out adjustment, in adjustment and allocates up the requisitioned stock to the SOP order. It is obvious the the stock requisition system must be very robust, coping with servers restarting, connection problems and other unforeseen events. It has to be able to roll forward or backwards any transactions that get interrupted half way through otherwise stock will “leak” causing no end of horrors! The control tables give full insight into where the stock has come from and which SOP order the stock ended up allocated to.

The control tables are monitored and written to by a windows service that runs on a server. The server process constantly looks at the control tables for new stock requests. The service uses eConnect to create the stock transactions in each company and also controls posting of the stock in the two companies via the Post Master application. Finally the windows service allocates up the stock to the SOP document requesting stock, once it has been successfully requisitioned from the source company.

When the user saves the SOP document in the reseller company, the lines on the order are scanned to see if any are held by the other company. If found and stock is available in the other company, a stock requisition request is made to the windows service by adding rows to the stock requisition control tables.

An independently developed stock allocation routine is ran using custom processes every hour to allocate SOP documents. This allocation process also checks over to the source company for available stock and requests stock from the requisition system if available. This deals with requisitioning items that have a back ordered quantity as they are received by the source company.

## Duplication of items

The items to be resold have to exist in the reseller company, integration manager helped duplicate the items over to the selling company. Going forward  critical fields like item description, bin locations are synced in SQL to the source company items so that any changes are correctly propagated over. Price list are created for the reseller company items independently so that they can be priced appropriately for that companies market.

## Fulfilment

Pick lists are printed with the locations and bins that were synchronised from the other company. As the items have been requisitioned into this reseller, they are fully fledged products in that company by this point. Thus fulfilment and despatch is no different for these items to any other in the company’s inventory.

## Deallocated stock

Deallocated stock due to cancelled lines or changed quantities show up as available stock in the reseller. The reseller should never hold free stock. There should never be available stock in the reseller as any stock is always associated with an order and will be allocated, thus free stock needs to go back to the source company. Pushing stock back to the source company it cam from, is simply a reverse process to that to used to requisition the stock in the first place. This free stock sweep is done regularly to ensure stock goes back to the source company as soon as possible in order to allow it to be sold there if required.

## Sock level visibility

Visibility of the stock is provided to the users through extra fields added to the SOP and Inventory enquiry windows. These windows display the stock available from the other company.

## Returned items from customers

Returned items are dealt with manually at the moment. The return has to be processed through both companies to keep everything right and proper.

## The first couple of months how it has gone…

The solution has been working well for a couple of months now, fully hands off, stock is merrily moving between companies with no admin or user intervention. Very satisfying solution indeed!

I’ve cut out some of the detail of this implementation as it is very specific to how GP is ran in our environment, but feel free to leave me a comment if you would like to know more about any aspects of this solution.

Searching from the start menu suddenly stopped working on one of my machines recently. Only when it stopped working did I realise how much I had started to rely upon it to find programs, configuration wizards and such like.

Trawling the forums unearthed many suggestions. Often the suggestions were to solve similar symptoms but did not resolve the issue I was experiencing. Starting to search would show ghosts of the category headings but not results.

You can see this in the screen shot below.

The solution that worked for me is the last one I give here but the journey can help find all the touch points for search.

## Check Customize Start Menu options

If it is merely the programs are not showing up then use the customise start menu, right click start menu>properties>customise. Ensure the search program files menu option is selected as shown;

From the control panel it is possible to manage the search index

## Use the windows search trouble shooter

There are many trouble shooters found here;

Control Panel\All Control Panel Items\Troubleshooting\All Categories

Amongst them is a search and indexing trouble shooter. Give it a try, it reported no problems for me even though obviously something was up.

## Check search is enabled as a feature

From Control Panel\Programs\Programs and Features go to windows features. This allows search as a feature to be turned on or off. Try turning off, reboot then turn on, reboot.

## Rebuild the index

By going to the search options you can click the advanced button and then rebuild the index.

## Finally fix the registry

In my case it was the following registry fix that worked.

Navigate to the following registry key;

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\FolderTypes\{EF87B4CB-F2CE-4785-8658-4CA6C63E38C6}\TopViews\{00000000-0000-0000-0000-000000000000}

Find the value GroupBy and if it is System.Null,  change it to value System.StartMenu.Group.

Killing explorer.exe process then re-launching it or rebooting will now allow the start search to work!