Friday, March 24, 2017

Musical Tribute: I Want My MVP

By Steve Endow

My colleague Andrew Dean and I have come up with a few wild ideas, mostly related to Dynamics GP development and customization.  The other day, he came up with a really, really wild idea.

I mentioned that MVP submissions were due at the end of March, so I had to finish up a few things in the next few weeks.

He said, "Why don't you make a video to the song Money for Nothing, but make it 'I Want My MVP'?"

That was one wild idea.  After I got over the surprise, I didn't know where to begin.  How does one make a music video, and how would I create the song?

With a little ingenuity, a friend who has a rock band, and some low budget guerrilla film making with my daughter, we pulled it together in 3 days.

Enjoy!




Lyrics

I want my MVP

I want my MVP

Now look at them users
That’s the way you do it
Getting help from those MVPs
That ain’t workin’ that’s the way you do it
Knowledge for nothin’ and your help for free

Now that ain’t workin’ that’s the way you do it
Lemme tell ya them users ain’t dumb
Maybe get an answer from the GP forums
Maybe get an answer from a GP blog

We gotta install all the latest patches
ERP software deliveries
We gotta move these big databases
We gotta write these SQL queries

See the busy worker with Excel and tons of numbers
Yeah buddy, no need to despair
That busy worker calls on the MVPs
That busy worker, he don’t need to care

We gotta install all the latest patches
ERP software deliveries
We gotta move these big databases
We gotta write these SQL queries

I shoulda learned to post a couple o’ questions
I shoulda learned to read some blogs
Look at that accountant, she’s got all the answers
Man accounting is so much fun
And he’s up there, what’s that? Reconciliations?
Bangin’ out reports like SQL wizardry
That ain’t workin that’s the way you do it
Get your knowledge for nothin’ get your help for free

We gotta install all the latest patches
ERP software deliveries
We gotta move these big databases
We gotta write these SQL queries

Now that ain’t workin’ that’s the way you do it
You get your help from an MVP
Now that ain’t workin’ that’s the way you do it
Knowledge for nothin’ and your help for free
Knowledge for nothin’ and help for free


Saturday, March 18, 2017

SQL Trivia: Leading spaces on string field

By Steve Endow

I think I've been writing SQL statements since 1996.  Maybe I encountered this scenario many years ago, but completely forgot about it.  But I am pretty sure that since I have been working with GP over the last 13 years, I can't remember ever encountering it.

During the SmartConnect training class that I attended this week, the trainer, Mark Anderson, pointed out a situation where an extra space could accidentally make its way into a SQL statement in a SmartConnect map.  He explained that the leading space in the WHERE clause value would cause the query to return zero records.

Somehow, I don't think I've run into this problem--I just haven't made that particular typo.

Here's an example of what that might look like.  The first query has no leading space in the WHERE clause, but does have trailing spaces, and it retrieves one customer record.  But the second query adds a few spaces in front of the customer ID value in the WHERE clause, and that query returns zero records.


Most Dynamics GP application fields do not seem to allow leading spaces--the only exception I could find is the Note field/window.  Given this, it is unlikely that you would have a situation where Dynamics GP data would have a leading space and cause this scenario.

However, if you have integrations or reporting with custom queries, or queries that are concatenated through script or code, it's possible for a space to creep in.  In SmartConnect, this can occur when you are inserting a pre-defined data field name into a SQL statement, like this:

SELECT * FROM RM00101 WHERE CUSTNMBR = ' _CUSTOMERID'

When you drag the _CUSTOMERID source data field name into your SQL script, it can result in an extra space before the field name, causing the SQL statement to return zero records.

Since I can't remember having ever encountered this scenario, it isn't something I would think to look for.  It makes sense, but it wouldn't have been obvious to me.  I've become almost indifferent to trailing spaces, because they have no consequence with a typical GP SQL install, but leading spaces are definitely a gotcha.

I thought it was interesting, and a neat reminder about the mechanics of SQL Server.


You can also find him on Twitter, YouTube, and Google+




Tuesday, March 7, 2017

SQL Script to verify Dynamics GP GL batch control totals

By Steve Endow

I have a customer who is importing GL batches into Dynamics GP, and they explained that they a bug in their integration that is causing the batch control totals to be incorrect. So they may import 50 transactions into GP, but the batch control total will show 85 transactions.  I believe that this discrepancy is causing some issues.

So I quickly threw together this SQL query to check the number of GL transactions and JE totals and compare those numbers to the batch control totals in SY00500.

There are probably several different ways to accomplish this, but I happened to pull this version together in a few minutes using a CTE.


WITH glTotals (BACHNUMB, BatchCount, BatchTotal)
AS (
SELECT glh.BACHNUMB, COUNT(glh.JRNENTRY) AS BatchCount, SUM(dtGLTotals.JEAmount) AS BatchTotal FROM GL10000 glh
JOIN (SELECT JRNENTRY, SUM(DEBITAMT + CRDTAMNT) AS JEAmount FROM GL10001 GROUP BY JRNENTRY) AS dtGLTotals ON dtGLTotals.JRNENTRY = glh.JRNENTRY
GROUP BY glh.BACHNUMB
)

SELECT b.BACHNUMB, b.NUMOFTRX, b.BCHTOTAL, glTotals.BatchCount, glTotals.BatchTotal, 
CASE WHEN b.NUMOFTRX <> glTotals.BatchCount THEN 'Count Diff' ELSE '' END AS CountDiff, 
CASE WHEN b.BCHTOTAL <> glTotals.BatchTotal THEN 'Amount Diff' ELSE '' END AS AmountDiff 
FROM TWO..SY00500 b 
JOIN glTotals ON glTotals.BACHNUMB = b.BACHNUMB
WHERE b.BCHSOURC = 'GL_Normal' AND b.BACHNUMB LIKE 'TEST%'


Here's what the results look like:


If there is a discrepancy in the transaction count or the total amount, it will display a note in the CountDiff or the AmountDiff columns.

Next, I will likely need to write a SQL script that will correct the control totals in SY00500.



You can also find him on Twitter, YouTube, and Google+





Monday, February 20, 2017

Converting a DataTable to objects in C#

By Steve Endow

Let's say you have a C# Dynamics GP integration that retrieves Customer records from a staging table and you need to convert each row to a Customer object so that you can more easily work with the data.

You could loop through the rows in your DataTable, read every field in the row, and assign each field value to each corresponding object property.  But that would be tedious, particularly if your object has dozens of properties.  And it's very low value coding.

After having done this a few times, I got sick of all of the typing and figured there had to be a better way.  Of course there is.  Fortunately a very smart person posted some code that handles this task very well, using Reflection.

https://codereview.stackexchange.com/questions/30714/converting-datatable-to-list-of-class

It works incredibly well and requires all of 1 or 2 lines of code to use.

I made a few different variations, allowing me to use it with a DataTable, DataRow, and DataRow arrays.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data;

namespace TSM.Integration.Library
{
    static class ObjectMapper
    {
        ///

        /// Converts a DataTable to a list with generic objects
        ///
        /// Generic object
        /// DataTable
        /// List with generic objects
        public static List DataTableToList(this DataTable table) where T : class, new()
        {
            //From: https://codereview.stackexchange.com/questions/30714/converting-datatable-to-list-of-class

            try
            {
                List list = new List();

                foreach (var row in table.AsEnumerable())
                {
                    T obj = new T();

                    foreach (var prop in obj.GetType().GetProperties())
                    {
                        try
                        {
                            PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
                            propertyInfo.SetValue(obj, Convert.ChangeType(row[prop.Name], propertyInfo.PropertyType), null);
                        }
                        catch
                        {
                            continue;
                        }
                    }

                    list.Add(obj);
                }

                return list;
            }
            catch
            {
                return null;
            }
        }


        public static T DataRowToList(this DataRow row) where T : class, new()
        {
            //Variant of DataTableToList code from: https://codereview.stackexchange.com/questions/30714/converting-datatable-to-list-of-class

            try
            {
                List list = new List();

                T obj = new T();

                foreach (var prop in obj.GetType().GetProperties())
                {
                    try
                    {
                        PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
                        propertyInfo.SetValue(obj, Convert.ChangeType(row[prop.Name], propertyInfo.PropertyType), null);
                    }
                    catch
                    {
                        continue;
                    }
                }

                return obj;
            }
            catch
            {
                return null;
            }
        }


        public static List DataRowArrayToList(this DataRow[] dataRowArray) where T : class, new()
        {
            //Variant of DataTableToList code from: https://codereview.stackexchange.com/questions/30714/converting-datatable-to-list-of-class

            try
            {
                List list = new List();

                foreach (var row in dataRowArray.AsEnumerable())
                {
                    T obj = new T();

                    foreach (var prop in obj.GetType().GetProperties())
                    {
                        try
                        {
                            PropertyInfo propertyInfo = obj.GetType().GetProperty(prop.Name);
                            propertyInfo.SetValue(obj, Convert.ChangeType(row[prop.Name], propertyInfo.PropertyType), null);
                        }
                        catch
                        {
                            continue;
                        }
                    }

                    list.Add(obj);
                }

                return list;
            }
            catch
            {
                return null;
            }
        }

    } 
}



You can also find him on Twitter, YouTube, and Google+










Thursday, February 16, 2017

Don't Forget- Standard GP SQL Views

From time to time, we still get inquires from folks building views in SQL server that actually already exist.  So I thought I would post a quick reminder that every SmartList has a SQL view that corresponds and can be used for your own purposes as well (e.g., SQL reports, Excel queries, SmartList Builder, etc).  And, remember, you can link views together as well as to other tables when creating reports.  Just don't modify the standard views (if you need to add to them, just create a new once with the same design and then modify).  Here are some of the most common ones available (this is NOT all of them) on any GP database...


  • AATransactions
  • Accounts
  • AccountSummary
  • AccountTransactions
  • BankTransactions
  • AttendanceDetail
  • BillofMaterials
  • CertificateList
  • Customers
  • EmployeeBenefit
  • Employees
  • EmployeeSummary
  • FixedAssets
  • FixedAssetsBooks
  • FixedAssetsPurchase
  • InventoryPurchaseReceipts
  • ItemQuantities
  • PayrollTransactions
  • PayablesTransactions
  • PurchaseLineItems
  • SalesLineItems
  • Vendors
When I teach beginner reporting classes, I advise students to always "look twice" for a standard view before embarking on creating new views or combining open/history/work tables in a SQL statement (as often the views already do this for you).  Good luck and happy reporting!


Christina Phillips is a Microsoft Certified Trainer and Dynamics GP Certified Professional. She is a director with BKD Technologies, providing training, support, and project management services to new and existing Microsoft Dynamics customers. This blog represents her views only, not those of her employer.

Customer Service and Failure

I hate car problems.  This is a fact of my life.  My dad was a car guy.  My brother is a car guy.  But I cringe every time I have to deal with car issues.  Fortunately, we have a mechanic who we trust and have taken both of our cars to for years.  (See the parallels already with a software partner/consultant?).  So, anyway, driving home last Friday my check engine light came on.  I did my normal scary internet searching for some basic things to try, and we cycled through those over the weekend (again, anyone picking up on the parallel to working with your software solution?).


Finally, on Tuesday, we caved and took it to our mechanic.  Who we like, but always secretly cringe because we don't know enough to know how much it will cost to fix.  Our mechanic fixed the issue (for those that are wondering- engine oil pressure sensor malfunction), although naturally it was a bit more than I wanted it to be (I wanted the under $100 fix of course!).  So I am sure by now, you are wondering why (despite the clever parallels) I am blogging about car problems on a blog devoted to Dynamics GP and software implementation?  Well, it is what came next that I think is a testament to how you think about customer service and approach failures with software and with partners.


On Wednesday morning, I woke up and got myself and the kids ready for the day.  I loaded the car with 20 cases or so of girl scout cookies (our office did a cookie pool to support all of the girl scouts in the office) then we loaded up to head to drop-off and work.  As soon as I started the car, I knew I had a problem- horribly rough idle and then the warning lights started flashing and next thing I know the car won't go faster than 10 mph.  Ugh. Ugh. Ugh.  Transfer cookies and kid and laptop to our other car, and call the mechanic.  When I talked to one of his employees, I was told they would either come out and get it or have it towed.  A couple hours passed, and I had not heard from them so I texted my husband to see if he had.  His text back was a simple "Yes, they came out and its fixed and they are test driving it around the neighborhood."


So there you go.  A mechanic who came to our house (his wife watched the store while he and his employee came out) and fixed something that was not tightened enough after the original repair.  Now, I know that some of you might think "dang right he came to your house to fix his own mistake" but I actually think of it totally differently.


Mistakes are inevitable in our work. We are human.  Software (and automobiles) are complicated.  We multi-task constantly with different clients, project, and even software.  Now, do we expect failures to be common?  No (this would be the first time in many years that we have had to call our mechanic back after a repair).  But I would argue that true customer service lies in how we respond to failures, do we....
  • Take on a proactive mindset?
  • Bring "solutions" to the table?
  • Skip the defensiveness and blame game?
  • Go the extra mile to resolve the issue?
I would argue that how we respond to failure as partners builds customer loyalty because failure is unavoidable at some point in a business relationship.  We deal with imperfect people, teams/organizations (clients and partners), and software.


In talking with the project managers where I work, we often discuss that projects will have bumps.  Trying to manage to avoid any bumps at all will leave you exhausted, ineffective, and reactionary.  But by understanding that projects will have bumps (miscommunications, missed expectations, etc) you are not "lowering the bar" (we continue to strive for excellence).  But you are, by expecting the occasional issue, adopting a proactive, pragmatic, and risk-adverse mindset- looking to manage the bumps, how we respond, and how we engage with the client for ultimate project success.


Look for the customer service in the failures.  That is where you will find it.  And that is where you will build the lasting partnerships (both internal and external) that will allow you and your organization to succeed.
Christina Phillips is a Microsoft Certified Trainer and Dynamics GP Certified Professional. She is a director with BKD Technologies, providing training, support, and project management services to new and existing Microsoft Dynamics customers. This blog represents her views only, not those of her employer.

Monday, February 6, 2017

Integration Manager eConnect Adapter Error: SQL Exception Thrown in the GetNextNumber method

By Steve Endow

I was trying to do a test in Integration Manager to confirm that the eConnect GetNextNumber methods work properly.  Simple enough.  I setup a test GL transaction with the eConnect destination adapter import and ran it.  After sitting for several minutes with no activity, I clicked on Stop and after a few more minutes, was finally able to see an error.


SQL Exception Thrown in the GetNextNumber method


Hmmm. Okay, so what is causing this?

I checked my SQL Server, but I didn't see any errors.  I then tried a SQL Profiler trace, but it just displayed some strange queries against the company databases with no references to GetNextNumber.  I checked SQL Activity Monitor, but didn't see any issues there either.  I was stumped.

Then I remembered one setting from when I created the integration.


Notice that the Server Name defaulted to localhost.  Since IM is running on the same machine as my SQL Server, I didn't think twice about it--even though I never use localhost for SQL Server connections (for a good reason, as I am demonstrating).

Well, the problem is that this server uses a SQL Server instance name, not the default SQL instance, so localhost will not work.  I changed the Server Name to the correct SQL Server instance name and the integration ran fine, no issues.

I'm puzzled why IM didn't time out or didn't log meaningful error messages, like Unable to connect to SQL Server, etc.  Fortunately, it was a simple enough fix, once I realized the problem.

Steve Endow is a Microsoft MVP for Dynamics GP and a Dynamics GP Certified IT Professional in Los Angeles.  He is the owner of Precipio Services, which provides Dynamics GP integrations, customizations, and automation solutions.

You can also find him on Twitter, YouTube, and Google+