Aaron Berquist Rotating Header Image

SQL

Populate Rounding Account On Multicurrency Payables Transactions Created Via eConnect

If you create multicurrency payables (PM) transactions using eConnect, and you are passing in the distributions manually (by setting CREATEDIST to 0), you may have encountered the same issue I encountered today.

When the transaction was created, eConnect also (correctly) added in a ROUND distribution, to account for 0.01 cents worth of FX related rounding on the transaction. Unfortunately, eConnect did not populate the distribution with an account. This struck me as strange – if you manually create a voucher in Dynamics GP and rounding is encountered, a ROUND distribution is created, and the account is populated (with the Rounding Difference Account, which is set up under Tools: Setup: Posting: Posting Accounts).

I should also mention – there is a specific order that should be followed when creating a multicurrency payables document – create the distributions first, then create the header record. Details are in VSTools thread here.

To resolve this issue, I entered some code into the taPMTransactionInsertPost procedure to retrieve the account and populate the distribution. The code snippet is below, or you can download it here.

/****** Object:  StoredProcedure [dbo].[taPMTransactionInsertPost]    Script Date: 07/15/2011 09:48:57 ******/
IF EXISTS ( SELECT  *
            FROM    sys.objects
            WHERE   object_id = OBJECT_ID(N'[dbo].[taPMTransactionInsertPost]')
                    AND type IN ( N'P', N'PC' ) )
    DROP PROCEDURE [dbo].[taPMTransactionInsertPost]
GO
 
/****** Object:  StoredProcedure [dbo].[taPMTransactionInsertPost]    Script Date: 07/15/2011 09:48:57 ******/
SET ANSI_NULLS ON
GO
 
SET QUOTED_IDENTIFIER OFF
GO
 
CREATE PROCEDURE [dbo].[taPMTransactionInsertPost]
    @I_vBACHNUMB CHAR(15) ,
    @I_vVCHNUMWK CHAR(17) ,
    @I_vVENDORID CHAR(15) ,
    @I_vDOCNUMBR CHAR(20) ,
    @I_vDOCTYPE SMALLINT ,
    @I_vDOCAMNT NUMERIC(19, 5) ,
    @I_vDOCDATE DATETIME ,
    @I_vPSTGDATE DATETIME ,
    @I_vVADCDTRO CHAR(15) ,
    @I_vVADDCDPR CHAR(15) ,
    @I_vPYMTRMID CHAR(20) ,
    @I_vTAXSCHID CHAR(15) ,
    @I_vDUEDATE DATETIME ,
    @I_vDSCDLRAM NUMERIC(19, 5) ,
    @I_vDISCDATE DATETIME ,
    @I_vPRCHAMNT NUMERIC(19, 5) ,
    @I_vCHRGAMNT NUMERIC(19, 5) ,
    @I_vCASHAMNT NUMERIC(19, 5) ,
    @I_vCAMCBKID CHAR(15) ,
    @I_vCDOCNMBR CHAR(20) ,
    @I_vCAMTDATE DATETIME ,
    @I_vCAMPMTNM CHAR(20) ,
    @I_vCHEKAMNT NUMERIC(19, 5) ,
    @I_vCHAMCBID CHAR(15) ,
    @I_vCHEKDATE DATETIME ,
    @I_vCAMPYNBR CHAR(20) ,
    @I_vCRCRDAMT NUMERIC(19, 5) ,
    @I_vCCAMPYNM CHAR(20) ,
    @I_vCHEKNMBR CHAR(20) ,
    @I_vCARDNAME CHAR(15) ,
    @I_vCCRCTNUM CHAR(20) ,
    @I_vCRCARDDT DATETIME ,
    @I_vCHEKBKID CHAR(15) ,
    @I_vTRXDSCRN CHAR(30) ,
    @I_vTRDISAMT NUMERIC(19, 5) ,
    @I_vTAXAMNT NUMERIC(19, 5) ,
    @I_vFRTAMNT NUMERIC(19, 5) ,
    @I_vTEN99AMNT NUMERIC(19, 5) ,
    @I_vMSCCHAMT NUMERIC(19, 5) ,
    @I_vPORDNMBR CHAR(20) ,
    @I_vSHIPMTHD CHAR(15) ,
    @I_vDISAMTAV NUMERIC(19, 5) ,
    @I_vDISTKNAM NUMERIC(19, 5) ,
    @I_vAPDSTKAM NUMERIC(19, 5) ,
    @I_vMDFUSRID CHAR(15) ,
    @I_vPOSTEDDT DATETIME ,
    @I_vPTDUSRID CHAR(15) ,
    @I_vPCHSCHID CHAR(15) ,
    @I_vFRTSCHID CHAR(15) ,
    @I_vMSCSCHID CHAR(15) ,
    @I_vPRCTDISC NUMERIC(19, 2) ,
    @I_vTax_Date DATETIME ,
    @I_vCURNCYID CHAR(15) ,
    @I_vXCHGRATE NUMERIC(19, 7) ,
    @I_vRATETPID CHAR(15) ,
    @I_vEXPNDATE DATETIME ,
    @I_vEXCHDATE DATETIME ,
    @I_vEXGTBDSC CHAR(30) ,
    @I_vEXTBLSRC CHAR(50) ,
    @I_vRATEEXPR SMALLINT ,
    @I_vDYSTINCR SMALLINT ,
    @I_vRATEVARC NUMERIC(19, 7) ,
    @I_vTRXDTDEF SMALLINT ,
    @I_vRTCLCMTD SMALLINT ,
    @I_vPRVDSLMT SMALLINT ,
    @I_vDATELMTS SMALLINT ,
    @I_vTIME1 DATETIME ,
    @I_vBatchCHEKBKID CHAR(15) ,
    @I_vCREATEDIST SMALLINT ,
    @I_vRequesterTrx SMALLINT ,
    @I_vUSRDEFND1 CHAR(50) ,
    @I_vUSRDEFND2 CHAR(50) ,
    @I_vUSRDEFND3 CHAR(50) ,
    @I_vUSRDEFND4 VARCHAR(8000) ,
    @I_vUSRDEFND5 VARCHAR(8000) ,
    @O_iErrorState INT OUTPUT ,
    @oErrString VARCHAR(255) OUTPUT
AS 
 
	/*
	AUTHOR: AARON BERQUIST
	DATE: 07/15/2011
	PURPOSE: POPULATE THE "ROUND" DISTRIBUTION AUTOMATICALLY CREATED WHEN
	A MULTICURRENCY PM TRANSACTION IS CREATED USING eCONNECT.
 
	BACKGROUND: WHEN CREATING A MULTICURRENCY PM TRANSACTION USING eCONNECT,
	IF THE "CREATEDIST" FLAG IS SET TO 0 AND DISTRIBUTIONS ARE BEING PASSED IN MANUALLY,
	THE SYSTEM WILL CORRECTLY CREATE A "ROUND" DISTRIBUTION (DISTTYPE = 16) IN THE PM10100 TABLE.
	UNFORTUNATELY, IT APPEARS THAT eCONNECT WILL NOT POPULATE THE DISTRIBUTION WITH THE ACCOUNT
	FROM THE ROUNDING DIFFERENCE ACCOUNT SETUP. IF YOU ENTER THE SAME TRANSACTION MANUALLY
	THROUGH THE GP APPLICATION, THE ACCOUNT IS AUTOMATICALLY POPULATED.
 
	TESTED ON DYNAMICS GP 10.00.1257.	
 
	*/
    SET nocount ON
    SELECT  @O_iErrorState = 0
 
    DECLARE @ROUNDINDEX INT
 
    --CHECK TO SEE IF THE INTEGRATION CREATED A ROUND ENTRY
    --IF SO, eCONNECT DOES NOT PICK THE DEFAULT ROUNDING ACCOUNT IF YOU CHOOSE TO MANUALLY CREATE DISTRIBUTIONS
    --SO THE POST PROCEDURE WILL READ THE ACCOUNT FROM POSTING ACCOUNT SETUP (TOOLS: SETUP: POSTING: POSTING ACCOUNTS: ROUNDING DIFFERENCE ACCOUNT)
    --AND POPULATE THE DISTRIBUTION
    IF EXISTS (SELECT 1 FROM PM10100 WHERE VCHRNMBR = @I_vVCHNUMWK AND DISTTYPE = 16 AND DSTINDX = 0)
    BEGIN
 
		--ROUNDING DIFFERENCE ACCOUNT IS SERIES 2, SEQNUMBR 900
		SELECT @ROUNDINDEX = ACTINDX FROM SY01100 WHERE SERIES = 2 AND SEQNUMBR = 900
		UPDATE PM10100 SET DSTINDX = ISNULL(@ROUNDINDEX,0) WHERE VCHRNMBR = @I_vVCHNUMWK AND DISTTYPE = 16 AND DSTINDX = 0
 
    END
 
    RETURN (@O_iErrorState)
GO
 
GRANT EXECUTE ON [dbo].[taPMTransactionInsertPost] TO [DYNGRP] AS [dbo]
GO

Post to Twitter

Use Dynamic SQL To Generate a Dynamics GP Login Macro

If you run Dynamics GP and have more than a handful of companies, I am sure you have felt the pain of installing or upgrading a 3rd party product, and then having to log in to each company as “sa” to complete the install process. I had to do this recently for a new 3rd party we are using (SmartView from eOne Solutions). Once the install is complete, a log in to each database as “sa” is required to create the necessary tables to support the tool.

Wanting to avoid the aggravation (and tedium) of repeating this process for the 150+ company databases we have, I wrote the SQL script below to generate a Dynamics GP macro which can be played which will log in to each database in sequence without needing any intervention.

It took me a while to figure out how the get the right “Item” number into the macro. I first thought it was the CMPANYID value from the SY01500 table, but that wasn’t right. Next I tried the DEX_ROW_ID value, but that wasn’t it either. Some trial an error finally yielded the right formula. The Item number is the index position in the dropdown list, ordered by CMPANYID! Despite the fact that the dropdown list displays items ordered by CMPNYNAM (in other words, alphabetically), it appears to first populate the list with the items ordered by CMPANYID and assigns them the item number based on that sequencing, then reorders the list to display by CMPANYNM.

/*
AUTHOR: AARON BERQUIST
DATE: 07/07/2011
PURPOSE: THE SCRIPT BELOW WILL GENERATE A DYNAMICS GP MACRO WHICH WILL LOG IN TO EACH COMPANY IN SEQUENCE.
USEFUL WHEN YOU HAVE INSTALLED A NEW 3RD PARTY PRODUCT, OR A SERVICE PACK,
AND EACH DATABASE NEEDS TO BE INITIALIZED BY LOGGING IN AS "SA".
 
TESTED WITH DYNAMICS GP 10, BUILD 10.00.1257
 
OVERVIEW:
 
THE SCRIPT BELOW BUILDS A MACRO WHICH CAN BE RUN IN DYNAMICS GP.
DEPENDING ON THE NUMBER OF COMPANIES YOU HAVE, THE OUTPUT OF THE DYNAMIC SQL BELOW WILL BE TOO LARGE TO BE
FULLY DISPLAYED IN THE RESULTS PANE OF MANAGEMENT STUDIO.
 
THEREFORE, THE DYNAMIC SQL CODE IS INSERTED INTO THE TABLE __MACRO.
 
USE MANAGEMENT STUDIO TO EXPORT THE CONTENTS OF THE TABLE, WITHOUT THE HEADER ROW, INTO A FLAT FILE WITH AN EXTENTION OF .MAC
THE FILE CAN THEN BE PLAYED INSIDE DYNAMICS GP.
 
USAGE:
 
1. EXECUTE THE SCRIPT BELOW - IT USES DYNAMIC SQL TO BUILD A LOGIN MACRO FOR ALL DYNAMICS GP DATABASES.
2. THE CONTENTS OF THE SCRIPT ARE INSERTED INTO THE TABLE __MACRO.
3. EXPORT THE CONTENTS OF THE TABLE TO A FLAT FILE, WITH A .MAC EXTENSION.
4. LOG IN TO ANY DATABASE AS "SA" AND RUN THE MACRO. THE MACRO WILL LOG IN TO EACH DATABASE IN ALPHABETICAL SEQUENCE.
 
POTENTIAL MODIFICATIONS:
 
WHEN INSTALLING SOME 3RD PARTY PRODUCTS, YOU MAY NEED TO CLICK "OK" ON A WINDOW AT LOGIN TO INSTALL, OR PERFORM SOME REPETITIVE TASKS.
THE MACRO CODE BELOW CAN BE MODIFIED TO SUIT YOUR NEEDS. THE "SPECIAL SAUCE" IS THE ROW_NUMBER() OVER CMPANYID.
 
*/
USE DYNAMICS
GO
DECLARE @ssQL VARCHAR(MAX) = ''
 
SELECT @sSQL = @sSQL+'  CommandExec dictionary ''default''  form ''Command_System'' command ''Switch Company''
NewActiveWin dictionary ''default''  form ''Switch Company'' window ''Switch Company''
  ClickHit field ''(L) Company Names'' item '+CONVERT(VARCHAR(3),ROW_NUMBER() OVER (order BY CMPANYID))+'  # '+LTRIM(RTRIM(CMPNYNAM))+'
  MoveTo field ''OK Button''
  ClickHit field ''OK Button''
NewActiveWin dictionary ''default''  form sheLL window sheLL
NewActiveWin dictionary ''default''  form sheLL window sheLL
ActivateWindow dictionary ''default''  form sheLL window sheLL '+CHAR(13)
FROM DYNAMICS..SY01500
ORDER BY CMPNYNAM
 
PRINT (@sSQL)
 
/****** Object:  Table [dbo].[__Macro]    Script Date: 07/07/2011 21:41:48 ******/
IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[__Macro]') AND type in (N'U'))
DROP TABLE [dbo].[__Macro]
GO
 
/****** Object:  Table [dbo].[__Macro]    Script Date: 07/07/2011 21:41:48 ******/
SET ANSI_NULLS ON
GO
 
SET QUOTED_IDENTIFIER ON
GO
 
SET ANSI_PADDING ON
GO
 
CREATE TABLE [dbo].[__Macro](
	[ssql] [varchar](max) NULL
) ON [PRIMARY]
 
GO
 
SET ANSI_PADDING OFF
GO
 
INSERT __MAcro SELECT @ssQL
 
--NOW EXPORT THE CONTENTS OF THE TABLE TO A FLAT FILE AND RUN IN GP.
--YOU CAN THEN SAFELY DROP THE TABLE __MACRO

You can either copy and paste the code above, or download a script file here.

Post to Twitter

Using SmartList Builder With A Linked Server – It Can Be Done!

We are looking to deploy SmartList Builder + SmartView as an enhanced reporting solution for our internal customers. One of the things I have seen floating around the net is that you can’t use SmartList Builder with a SQL Linked Server. In fact, a number of Dynamics MVP’s alluded to this in a support thread at http://www.ms-news.net/f1610/smartlist-builder-odbc-error-8692687.html. The actual error message will look something like this:

GPS Error 58

SQL Error 7405 ODBC SQL Server Driver

Heterogeneous queries require the ANSI_NULL and ANSI_Warnings options to be set for the  connection.
This ensures consistent query semantics. Enable these options and
then reissue your query.

ODBC error 37000

I’ll show a little further into the article that yes, you can work around this, and pretty easily too – it just takes a little sleight-of-hand with your SmartLists.

A little background – we have 150+ Dynamics GP companies, all on one database server. We also frequently have requests to produce reports which consolidate data from all of these entities onto one SmartList so that, in one report, users can quickly see data from every entity. Up until now, we have been using the technique I outlined here to create reports which use a linked server to execute a stored procedure inside a view using OPENQUERY. In order to make this work, I had to come up with a workaround to the very ANSI_NULLs issue other people have reported as stopping them in their tracks.

The secret boils down to this – you have to trick AnyView, or SmartList Builder into accepting the view the first time you add it. Once you do that, and build your SmartList, you can change what the view is actually doing after the fact. I know that it works with AnyView, and have done a test with SmartList builder and it works there as well.

For the steps below, assume the final query you want to use looks something like the code below. Column1 is varchar(5) and Column2 is TINYINT:

select Column1, Column2 from LINKEDSERVER.SAMPLEDB.dbo.SAMPLETABLE

Here are the steps you need to follow:

Create a SQL view with the columns and field lengths that match the “final” report you want to produce:

CREATE VIEW SampleView
 
AS
 
SELECT convert(varchar(5),'') as Column1, convert(tinyint,0) as Column2
 
GO
 
GRANT SELECT ON SampleView TO DYNGRP

Add that view to SmartList Builder / AnyView and build your SmartList off that view.

Once you have successfully built the object, ALTER the view to call the code from the linked server:

ALTER VIEW SampleView
 
AS
 
--SELECT convert(varchar(5),'') as Column1, convert(tinyint,0) as Column2
select Column1, Column2 from LINKEDSERVER.SAMPLEDB.dbo.SAMPLETABLE

 

Voila! Your SmartList will run the linked server code, with no ANSI_NULL warnings!

I have used this approach for most of the SmartLists we have in Production today, with great success. Of course, using a linked server is not, in the end, an ideal solution, but for cases when you have to, the method above will allow you to use SmartLists with SQL Linked Servers successfully.

Note: I have updated the sample code above to include the “GRANT SELECT” permissions to “DYNGRP” on the view. If you don’t do this, SmartView will spin forever on the “Loading…” screen when you try to run a SmartList.

Post to Twitter

Troubleshooting Upgrade to Dynamics GP 2010 R2, Part 2

Another error encountered, another error resolved.

During the upgrade of the sample database, TWO, I received an error on the procedure SVC_MailProcessLOG. The error message was:

ERROR [Microsoft][SQL Server Native Client 10.0]TCP Provider: The specified network name is no longer available.

That incredibly helpful error message really means: Perform your GP Utilities run on the SQL Server itself.

I made the mistake of running GP Utilities from one of the client machines, not from the actual SQL Server. It appears that something in that procedure causes an issue when you’re trying to create it from a client machine, over the network, rather than from the local SQL Server itself.

Another good reason to RTFM, I guess :)

Post to Twitter

Troubleshooting Upgrade to GP 2010 R2

I’ve been working on installing the newly released GP 2010 R2 in a “sandbox” environment. I ran in to an issue performing the upgrade and thought I’d share it in case it helps anyone else.

I had the GP 2010 client installed on the same machine I was installing R2 on. Rather than creating a new DSN at install time, I simply renamed the existing DSN used for GP 2010 and moved on to GP Utilities.

However, the upgrade kept crashing at the “syExcelReports” step. The crash report indicated an issue with sqlncli.exe.

After trying a few other things, I finally thought about what the sqlncli.exe message might mean. Going in to the DSN setup (c:\windows\syswow64\odbcad32.exe on my 64-bit machine), I noticed that the original GP 2010 DSN I used was using an old version of the SQL Client.

I created a new DSN using the SQL Native Client 10.0 driver, re-ran the upgrade using that DSN, and voila! Upgrade completed. I hope this tip saves someone else some time and frustration as they work through their install/upgrade of GP2010 R2.

 

Post to Twitter

In Praise of FieldService, Or: Product 949, You Sure Are Fine

Having spent the better part of my career working with Dynamics GP, I’ve had the pleasure of working with most of the core modules, and a number of 3rd party applications as well. By far and away, my favourite module to work with has been the FieldService module, and more specifically, the Contracts Administration portion of it. I find it’s well designed, well written, and very easy to enhance and extend when the need for custom business processes dictates.

After reflecting on why it is I find this to be my favourite module, it comes down to the fact that so much of it is based on SQL stored procedures. This fact alone has enabled us to make some very useful and powerful changes to the behaviour of certain parts of the module in a fairly easy and straightforward manner. Some of the changes we’ve made by modifying certain stored procedures are:

  1. Create SOP orders with a series of items, then create contractible items based on those items. (Procedure modified: SVC_Create_Cont_Line_From_SOP)
  2. Automatically create/change/delete items on a contract at renewal time. (Procedure modified: SVC_Contract_Renew)
  3. Repurpose certain fields and tables for other uses (Procedure modified: SVC_Create_Cont_Line_From_SOP).
  4. Eliminate contracts from appearing in the Contract Move screen based on certain criteria (Procedure modified: SVC_Create_Contract_Move_WORK)

In most cases, a combination of modifying the stored procedure, plus some “light” VBA coding is all that’s required to implement the custom business logic we require. It would be fantastic if other modules implemented more of a “stored procedure” based approach.

In short, for the developer who does not know Dexterity, the implementation of the FieldService module, with it’s heavy use of SQL stored procedures, makes it pretty straightforward to enhance the application in a rapid and efficient way.

Post to Twitter

Tool for Improving “Mark All” Speed in Contract Administration

One of my users complained recently that, when using the “Mark All” button on the Revenue Recognition screen in Contract Administration (Dynamics GP: Transactions: Contract Administration: Revenue Recognition), it would sometimes take 6-8 hours for the process to complete. This was not to post the revenue, merely to mark all of the records in the window for processing.

Now, in some of our larger company databases, over 30,000 records were being marked to process, due to a combination of:

  • # of contract records
  • the business process of recognizing multiple months worth of revenue

Clearly, this is not an acceptable amount of time to wait for a process to complete. I arranged to do a SQL trace while the user clicked the “Mark All” button. I found that the Revenue Recognition window would run a SQL stored procedure (SVC_Check_Contract_Revenue) for every contract record in the window to determine if the contract was “Marked to Post” by another user. If so, a message would display, alerting the user to the fact that another user had a record marked to post. Technically, the code was:

  • examining the SVC00625 table (SVC_Contract_Revenue_WORK)
  • looking at each contract/fiscal period combination to see if another user also had that contract/fiscal period record marked to post (MKDTOPST = 1).

The key thing I found was that this routine was being run row-by-row, one record at a time. Even though the check was very quick to complete for any one given record, the number of records it had to check added up to the 6-8 hours I mentioned earlier. Fortunately, I was able to devise a solution to this issue. In reality, my users did not care if someone else posted the record, and did not need to know which contract in question was going to be posted by someone else. It was enough for them to know “Hey – some of the records you want to mark are currently marked by someone else. I’m going to skip marking those particular records, and just mark the ones that are safe to post.” By taking this approach, I could craft a routine which would work on ALL records at once, which was orders of magnitude faster.

The solution I devised to this problem involved 4 components:

  1. I modified the Revenue Recognition window to add my own “Mark All” and “Unmark All” buttons. I hid and disabled the actual “Mark All” and “Unmark All” buttons in Modifier.
  2. I created a custom stored procedure (X_OT_SVC_REVREC_MARK_UNMARK_ALL) to mark all/unmark all records to post. It will skip any records marked by other users, and raise an alert if any are found.
  3. I added VBA to the window which will call the procedure when either of the buttons are clicked.
  4. I noticed that the window did not refresh to show the buttons as checked/unchecked after the stored procedure was run. I also noticed that if I clicked the “down” arrow on the keyboard, the line would refresh. So, I recorded a macro of pressing “down” 16 times (as that’s the number of rows that show in the window) which is then called from the VBA code.

If you’re experiencing slow performance when marking/unmarking all, try using the code pack below to implement this solution in your environment (test first!). Instructions on how to install are included, as there are a few tweaks you will have to make based on how your environment has been crafted. If you have questions, or if this solution helps you out, please leave a comment below!

Download the package here: Enhanced Mark All Revenue Recognition Routine.zip

Post to Twitter

Quickly Add Users to POWERUSER Role in Dynamics GP 2010

One of the more tedious processes in configuring a “demo” or “test” system is setting up user accounts. Recently, I needed to configure a GP 2010 “Sandbox” for some internal users who wanted to look at the new features Dynamics GP 2010 has to offer.

After manually creating their accounts (Dynamics GP: Tools: Setup: System: User Setup), and granting access to the Fabrikam company (Dynamics GP: Tools: Setup: System: User Access), I wrote a quick script to add all users to the POWERUSER role, with the DEFAULTUSER Alternate/Modified Forms and Reports ID.

The script adds records to the SY10500 and SY10550 tables for all users in the SY01400 table, excluding “sa”:

 
/*
AUTHOR: AARON BERQUIST - WWW.AARONBERQUIST.COM
DATE: 11/25/2010
PURPOSE: THIS SCRIPT WILL ADD EVERY DYNAMICS GP USER TO THE POWERUSER ROLE, IN FABRIKAM, INC.
IT WILL ALSO SET THEIR ALTERNATE/MODIFIED FORMS AND REPORTS ID TO DEFAULTUSER
USAGE: USE THIS SCRIPT TO QUICKLY SET UP USERS FOR ACCESS TO A TEST OR DEMO SYSTEM
 
TESTED ON DYNAMICS GP2010, BUILD 11.00.1524 (SP1)
*/
 
USE DYNAMICS
GO
--PROCESS THE ROLES
INSERT INTO SY10500
	(USERID,
	CMPANYID,
	SECURITYROLEID)
SELECT
	sy4.USERID,
	-1, --THIS IS THE CMPANYID FOR FABRIKAM (AKA TWO),
	'POWERUSER' --FOR THE PURPOSES OF THE DEMO SYSTEM, ALL USERS HAVE FULL RIGHTS TO THE APPLICATION
FROM
	--USER TABLE
	SY01400 sy4
	LEFT JOIN SY10500 sy5 on sy4.userid = sy5.userid
WHERE
	sy5.USERID IS NULL 
	--WE DO NOT WANT TO TOUCH THE "SA" ACCOUNT IN ANY WAY
	AND sy4.USERID <> 'sa' 
GO
--PROCESS THE AFA'S
INSERT INTO SY10550
	(USERID,
	CMPANYID,
	SECMODALTID)
SELECT
	sy4.USERID,
	-1, --THIS IS THE CMPANYID FOR FABRIKAM (AKA TWO),
	'DEFAULTUSER' --FOR THE PURPOSES OF THE DEMO SYSTEM, ALL USERS WILL HAVE THE DEFAULTUSER AFA
	FROM
	--USER TABLE
	SY01400 sy4
	LEFT JOIN SY10550 sy5 on sy4.userid = sy5.userid
WHERE
	sy5.USERID IS NULL 
	--WE DO NOT WANT TO TOUCH THE "SA" ACCOUNT IN ANY WAY
	AND sy4.USERID <> 'sa'

Download the script here: GP 2010 – Configure Users for Demo System.sql

Post to Twitter

Easily Add New Databases to Global Dynamics GP Reports

Last post I explained how using the OPENQUERY SQL syntax can help skirt the 260 tables in a view limit of SQL Server. Today I’ll show how to combine that tip with additional logic to automatically add new Dynamics GP company databases to your custom global reports.

If you have a large number of global reports, or even if you only have a few, maintenance can be a hassle whenever a new Dynamics GP company is added to your system. I have devised a solution to this maintenance headache by combining a custom table which holds the name and code for my global reports, with a nightly procedure which will rebuild all of the global objects automatically. The items that make up the solution are:

1. A custom table, stored in DYNAMICS, with the following structure:

  • Report ID – a unique identifier for the report
  • Report Name – the name of the SQL object
  • Report Code – the report code I want to run against every Dynamics GP database
  • Report Type – whether the SQL object to build is a stored procedure or a view

An example of what a record in the table would look like is:

Report ID Report Name Report Code Report Type
1 usp_NonVoidedSOPDocs Select * from REPLACEME..SOP30200 where VOIDSTTS = 0 Stored Procedure

Note the “REPLACEME” line in the “Report Code” field. This value will be replaced with the database name when the item below is run.

2. A stored procedure, also stored in DYNAMICS, which is called every night from a SQL Job. This procedure does the following for each entry in the table above:

  • Gets a list of database from the SY01500 table in DYNAMICS. The field it returns is INTERID.
  • Dynamically builds a SQL statement which will DROP the existing SQL object, based on the Report Name and Report Type values from the table.
  • Replaces the value REPLACEME in the Report Code string with the INTERID value.
  • Dynamically builds a SQL statement to CREATE the SQL object, based on the Report name and Report Type values from the table.
  • Grants EXECUTE permissions to DYNGRP to the SQL Object.

As an example, thejob above would dynamically generate, then execute, the SQL String below:

USE DYNAMICS

GO

IF EXISTS (SELECT name from sysobjects where name  = ‘usp_NonVoidedSOPDocs’)

BEGIN

DROP PROCEDURE usp_NonVoidedSOPDocs

END

GO

CREATE PROCEDURE usp_NonVoidedSOPDocs as

SELECT * FROM COMPANY1..SOP30200 WHERE VOIDSTTS = 0

UNION ALL

SELECT * FROM COMPANY2..SOP30200 WHERE VOIDSTTS = 0

UNION ALL

SELECT * FROM COMPANY3..SOP30200 WHERE VOIDSTTS = 0

GO

GRANT EXEC ON usp_NonVoidedSOPDocs to DYNGRP

By using this technique, new Dynamics GP company databases can be automatically added to your custom reports without any intervention on your part – freeing you up to work on other tasks.

Next week, I plan to talk about why I am such a fan of the Field Service module in Dynamics GP and how the way it has been designed is such a boon to analysts and developers who need to customize it’s behaviour.

Post to Twitter

Creating Global Dynamics GP SmartLists with OPENQUERY

From time to time, I have needed to create a SmartList which pulls in data from multiple Dynamics GP company databases, with multiple tables. Because of the number of databases involved, and the number of tables, I hit the “260 tables in a view” limit of SQL Server. Because of the specific needs of this particular business unit, the report needed to be a SmartList, and I could not deliver it via Reporting Services. 

After trying a number of different approaches, I stumbled upon using the SQL command OPENQUERY to call a stored procedure which obviously is not encumbered with the 260 table limit. 

The steps I took to utilize this feature are as follows: 

  1. Create a linked server in SQL which pointed back to itself. In my case, I named the linked server “SMARTLIST”
  2. Write the view in a single database.
  3. Deploy the view against all of the databases in Dynamics GP.
  4. Create a stored procedure in one database which unioned all of the views together.
  5. Create a view in one database which called the stored procedure using OPENQUERY, like so: SELECT * FROM OPENQUERY(SMARTLIST, ‘exec MyProcedure’) AS derivedtbl_1 
  6. Build a SmartList off of the view in question.

Voila! Now I have a view I can use in my SmartList which gets around the 260 table limit in SQL Server.

In an ideal world, this report would be delivered via another mechanism, such as Reporting Services, or a consolidated reporting server. However, sometimes a less-than-ideal solution is needed and this is one trick to keep in mind should your design constraints dictate going with a SQL View.

Next week I’ll expand on this item and show how you can dynamically generate a view which will automatically add new Dynamics GP company databases to your global reports.

Post to Twitter