Thursday, April 30, 2015

PriceList_DefaultPrice of ivPriceMatrix not returning a price

I turned on script log and saw that it's seeing the Default UM as empty. That's weird, because i'm passing in the IV_Item object with the table, and it clearly has a Default UM.

Then I copy and pasted the GP code into my program so that i could put breakpoints and step through the code. It turned out that the IV_Item object's table reference was empty by the time it reached the PriceList_DefaultPrice function.

Why?

Well, I created the IV_Item object in a form proc using that proc's table buffer. Once the scope left that proc, the table buffer was discarded, and therefore resulted in the IV_Item's table reference going bye bye.

Solution
Create the IV_Item object with a table buffer that stays in scope when calling PriceList_DefaultPrice. I added an IV_Item object to my scrolling window, and then passed in a table buffer (which is then in scope of the scrolling window) to the proc that creates the IV_Item object. Because the table buffer was in scope, it could find the Default UM and spit out a price!

Monday, April 27, 2015

Dexterity - next doc num template

Having automatic 'next doc num' requires many different parts if you want to make sure to release unused next doc nums. For example, if a user clicks into a Doc Num field that is prepopulated with the next doc num, but then changes it, you'll want to release the prepopulated number, so numbers aren't wasted.


1. Setup window / table
Add your 'next doc num' to a setup window. The table may be a company-wide setup table, just like POP_Setup. Or it could be partitioned on "something", like document type, like SOP_ID_SETP.

2. Utility functions for getting next number

Note: I pass in an anonymous table, so that I can write unit tests and pass in a temp table instead of the actual setup table. This shields my setup data from getting damaged.

GetNextDocNum
function returns NextDocNum NextDocNum;
inout integer Status;
inout anonymous table tbl; {Anonymous so we can put this under test without affecting actual data}

local integer i = 0;
local boolean Success;
local string tmpNextDocNum;

Status = NextDocNumExists(table tbl);

if Status <> OKAY then
abort script;
end if;

NextDocNum = NextDocNum of table tbl;

{Loop 1000 times until we find a number that isn't used}

repeat
if not Exists(NextDocNum) then
exit repeat;
end if;

call Document_Number_Inc_Dec, NextDocNum, true, Success;
increment i;
until i >= 1000;

if i = 1000 then
Status = PROBLEM_GETTING_NEXT_NUM;
clear NextDocNum;
release table tbl;
else
{Because this is a valid next doc number, we want to increment the one in the table
to one after this}
tmpNextDocNum = NextDocNum;
call Document_Number_Inc_Dec, tmpNextDocNum, true, Success;
if not Success then
Status = PROBLEM_GETTING_NEXT_NUM;
clear NextDocNum;
else
NextDocNum of table tbl = tmpNextDocNum;
save table tbl;
if err() <> OKAY then
Status = PROBLEM_GETTING_NEXT_NUM;
clear NextDocNum;
end if;
end if;

end if;

NextDocNumberExists
function returns integer Status;
inout anonymous table tbl;

'Setup Key' of table tbl = 1;
change table tbl by number 1;
if err() <> OKAY then
Status = PROBLEM_GETTING_NEXT_NUM;
release table tbl;
abort script;
end if;

if empty(NextDocNum of table tbl) then
Status = NO_NEXT_NUM_SETUP;
release table tbl;
abort script;
end if;


Status = OKAY;

Exists
function returns boolean Exists;
in NextDocNum NextDocNum;

NextDocNum of table TransactionTable = NextDocNum;
get table TransactionTable by number 1;

Exists = (err() = OKAY);

ReleaseUnusedDocNum
{
Release the rejected num back into the wild
}

in NextDocNum NextDocNum;
inout anonymous table tbl;

if not Exists(NextDocNum) then
'Setup Key' of table tbl = 1;
change table tbl by number 1;
if err() = OKAY then
NextDocNum of table tbl = NextDocNum;
save table tbl;
end if;
release table tbl;
end if;

3. The window that uses the next number
Doc Num Pre Script - this populates the Doc Num field with the next doc num when the user clicks into the field
local integer Status;
local string tmpNum;

if empty(NextDocNum) then
tmpNum = GetNextDocNum(Status, table TheRealSetupTable);
if Status <> OKAY then
warning getmsg(Status);
abort script;
end if;

NextDocNum = tmpNum;
'(L) TempOldDocNum' = NextDocNum;
force change NextDocNum;
end if;
Doc Num Change Script
if '(L) TempOldDocNum' <> DocNum and not empty('(L) TempOldDocNum' ) then
{this means they didn't accept the populated doc num, so release it back into the wild}
call ReleaseUnusedDocNum, '(L) TempOldDocNum', table TheRealSetupTable;
clear '(L) TempOldDocNum';
end if;

TempOldDocNum Change Script -  This is very similar to the Doc Num Change Script, but look at the condition + which value is passed to ReleaseUnusedDocNum
if '(L) TempOldDocNum' = DocNum and not empty('(L) TempOldDocNum' ) then
{This means they're deleting the pre-populated value, so delete it}
call ReleaseUnusedDocNum, DocNum, table TheRealSetupTable;
clear '(L) TempOldDocNum';
end if;

HandleChanges - if they are discarding their changes

 if '(L) TempOldDocNum' = DocNum then call ReleaseUnusedDocNum, DocNum, table TheRealSetupTable; clear '(L) TempOldDocNum'; end if;

Call TempOldDocNum in
1. Clear Button
2. Win Post when there are no changes

Thursday, April 23, 2015

Dexterity - browse button template





Call the Browse procedure from each browse button, passing in a different operator constant depending on which button it is.

Example of the call: call Browse of form MyForm, table MyTable, GET_NEXT;

Note: This template only searches on key 1. You'd have to modify this to work with any specified key (like the Sort By field).


Form procedure - Browse

inout table MyTable;
in integer Op;
default window to MyWindow;

if (changed(window MyWindow)) and not empty(ID) then
run script 'Handle Changes';
if not 'Handle Changes' then
   ID of table MyTable = ID;
abort script;
end if;
clear changes form MyWindow;
end if;

case Op
in [GET_NEXT]
get next table MyTable by number 1;
in [GET_PREV]
get prev table MyTable by number 1;
in [GET_LAST]
get last table MyTable by number 1;
in [GET_FIRST]
get first table MyTable by number 1;
end case;
if err() <> OKAY then
abort script;
end if;

'(L) TempID' = ID of table MyTable;

run script '(L) TempID';

Tuesday, April 21, 2015

Starting to use TDD in .NET

I'm currently reading the book Working effectively with Legacy Code, and the main theme is to try to cover all code with unit tests. If the code is difficult to test the main cause is dependencies, and there are a number of ways to deal to break those.

Today I was working on adding a new feature to existing legacy code in C#. I decided to try out the TDD method of doing things. I was successfully able to add unit tests for most of the new code. 

1- changing code that uses RMO to manage replication - i could not figure out an easy way to break dependency, so I tried to use sprout method. That turned out to not work either, so then I was going to refactor the current code so that I could add the sprout method. Then I realized that the end result would be a very trivial method, with the sole purpose of being easier to unit test. So I gave up on this, and did the old way - integration test and then manually checking the results.

2. Changing SQL scripts. These are actually dynamic SQL for creating stored procs, so I added unit tests for the script installer. This was an unforeseen benefit of having to use dynamic SQL - it's covered by tests and since the stores procs are actually created it verifies the syntax and objects. This is an advantage over executing scripts with NOEXEC, or executing them in a transaction. 

3. Parsing data from a SQL query into a data class - the parser method has a SqlDataReader parameter like this

function(SqlDataReader reader)
{
parse reader
}

So the method is dependent on that class. The book talks about using fake / mock objects in order to break dependencies. So I changed the parameter to ask for an IDataRecord, which is an interface that SqlDataReader implements. Then I subclassed IDataReader and implemented the methods that my method uses. I called this class FakeSqlDataReader. Then I created the object, populated it with data, and passed it to the parser. Voila, broke that dependency and got the code under test!

Friday, April 10, 2015

Dexterity - generalized pass-through SQL Error Handler

{
Clean Code approach to pass-through SQL. Eliminates the need to constantly put this error handling in every time pass-through SQL is getting executed
}

inout long SQLConnection;

local string cantGetError = "Could not retrieve SQL error info.";
local integer nStatus;
local long sqlConnection, status;
local text SQL;
{SQL error information}
local long GPS_error_number, SQL_error_number;
local string SQL_error_string, ODBC_error_string;

if SQL_GetError(sqlConnection, GPS_error_number, SQL_error_number, SQL_error_string, ODBC_error_string) = OKAY then
SQL_Terminate(sqlConnection);
throw SQL_EXCEPTION, 0, SQL_error_string;
else
SQL_Terminate(sqlConnection);
throw SQL_EXCEPTION, 0, cantGetError;
end if;

Dexterity - template for executing SQL and getting a result

{ Clean Code approach to pass through SQL }

pragma(disable warning LiteralStringUsed);
local long sqlConnection, status;
local text SQL;

local long numRecords;


if SQL_Connect(sqlConnection) <> OKAY then
throw SQL_EXCEPTION, 0, "Connection to SQL has not been established.";
end if;


SQL = " USE " +  trim('Intercompany ID' of globals);
SQL = SQL + " SELECT COUNT(*) FROM " + physicalname(table myTable);
debug SQL;

if SQL_Execute(sqlConnection, SQL) = OKAY then
status = SQL_FetchNext(sqlConnection);
status = SQL_GetData(sqlConnection, 1, numRecords);
else
call SQLErrorHandler, sqlConnection;
end if;

status = SQL_Terminate(sqlConnection);

pragma(enable warning LiteralStringUsed);

Thursday, April 9, 2015

Dexterity - Test Mode

1."You need to run Dex Utilities", then clicking on OK does nothing
Add this to your Dex.ini
Synchronize=False
https://community.dynamics.com/gp/f/32/t/47585.aspx

2. Missing table syUserDefaults 
Add this to your Dex.ini
Pathname=DYNAMICS/dbo/


3. EXCEPTION_CLASS_SCRIPT_MISSING 'SQL Login l_Login_CHG
I removed Dexterity and reinstalled it.

4. Missing table "CustomizationMaintenance" or something similar
Make the Startup script not try to create tables 


5.  Could not load file or assembly 'Microsoft.Dynamics.GP.BusinessIntelligence.Homepage' or one of its dependencies. The system cannot find the file specified.
Copy these two files from the GP folder to the Dexterity folder:
Microsoft.Dynamics.GP.BusinessIntelligence.Homepage.Framework.dll
Microsoft.Dynamics.GP.BusinessIntelligence.Homepage.dll

http://blogs.msdn.com/b/developingfordynamicsgp/archive/2012/08/15/could-not-load-file-or-assembly-microsoft-dynamics-gp-businessintelligence-homepage-dll.aspx

6. Any cross dictionary errors
Use isTestMode() of form LibSystem to conditionally call cross dictionary scripts, and triggers.

7. GP Lookup windows result in "Cannot access this form because the dictionary containing it is not loaded"
In Alternate/Modified Forms and Reports find the lookup. It should have neither Dynamics.dic or your.dic radio buttons selected (it resides in the SmartList dic), so select your.dic and save. Voila, you can now open the lookups.

8. Unhandled script exception: SCRIPTS - data area EXCEPTION_CLASS_SCRIPT_MEMORY
Copy GPDWin32.dll from Dynamics GP folder to your Dexterity folder 
https://community.dynamics.com/gp/f/32/t/146623 

9. EXCEPTION_CLASS_SCRIPT_ADDRESSING 'SQL Login l_Login_CHG
The error message isn't helpful at all. In my case there were missing base/Globals variables in my dictionary. Not sure how that happened. To fix this I did the following:
    a. Open up the source control project
    b. Locate Base/globals.global
    c. Check out and edit with notepad
    d. Find and fix the missing variables. In my case I had two missing variables, AuthenticationType and SQLLoginID. Before fixing them they looked like this:
       - SystemVariable "AuthenticationType"
      I looked in a working dictionary's globals and saw it should be this:
        = SystemVariable "AuthenticationType"
{
Position "00353"
}
     e. Check in the change
     f. Fetch base/Globals in the dictionary

Startup
In my Startup script i have the following trigger registered:
call checkTriggerError,  Trigger_RegisterFunction(function OK_Click of form 'Switch Company',

    TRIGGER_AFTER_ORIGINAL, script InitTestMode), "InitTestMode";

InitTestMode launches a testing form and sets globals needed for testing.

Wednesday, April 8, 2015

Tuesday, April 7, 2015

Dexterity - multi-purpose button for a window that is both an Inquiry and Lookup

The window has two modes:
Inquiry - button should say OK, and simply close the form
Lookup - button should say Select, and return the selected record

There's two ways to do this:
Multiple buttons - swap / hide the button based on what mode is being used.

1 Multi-purpose button - Add two images to the button, and use Field_SetImage(), Field_SetCaption(), and Field_SetBooleanProperty(field, FIELD_PROP_DEFAULT, bool) depending on the mode. In the change script use Field_GetImage() to retrieve the image index - which tells you the mode.

I've done this both ways, and I prefer the multi-purpose button. The more modes that are needed, the better the multi-purpose button solution becomes - because you have fewer fields to juggle around.

Dexterity - Filtering a scrolling window and using a seed

Problem
I have a scrolling window where I want to filter and only show a subset of the records, and then I also want to use a seed so that it scrolls to that line automatically.

Solution
1. Figure out what you're going to set the seed as.
2. Add a key for all the fields in the seed.
3. Set a range on the table using the seed's key
4. Set the seed fields in the table
5. fill window LineScroll from current using seed's key.

Notes on 'Pragmatic Thinking and Learning: Refactor Your Wetware'

Finished this book. I liked it. Here's my raw notes. I might go through this and organize it at some point.

-always have something around to write ideas.
Ex: when coding have a word doc open. Today I came up with several ideas. Design questions, performance optimizations. I usually have some medium with me to write on.

-learning - l-mode favors analysis. R-mode favors synthesis. 
Ex: I like to build prototypes, do unit testing, and write test sql statements. That's learning by synthesis. Good!

-positive emotions and being happy further enable creative thinking

-aesthetics matter for brain neurogenesis. In other words, good looking stuff makes your brain awesomer.

-Neuroplasticity - your brain can become stronger just by believing I can learn anything, and by practicing

-time pressure has terrible effects. It shuts down the r-mode, the creative side. Give yourself permission to fail. How i thought of this while i was reading it was "how do you deal with pressure? You don't deal with it." in other words, don't give into pressure. 

-drawing is an r-mode activity, draw more. Drawing meditation. Being in the flow is what happens when the r mode takes over. 

-R-mode to L-mode flow. Start by doing something and then get the formal lecture. 

-lead with R-mode, follow with L-mode. R mode is like doing whiteboard design. L mode is hammering out the details 

-pair programming or stepping away from the keyboard once in awhile. I think I do this often, by reading Facebook, playing games

-use metaphors to explain things

-ask a problem, then close my eyes, observe the images that come to mind. Analyze the images. This strengthens l to r mode.

I think I've been doing a lot of this stuff before, I just didn't know why. When I'm working on a hard problem I think of solutions at random times. The async r-mode explains why that happens.

-write more. Use blogger to write out ideas. Write on paper. I kinda do this a lot already. Write more

-step away from the computer when trying to solve hard problems. Take a walk, etc.., but don't actively think about the problem. Focus on the simple task at hand - walking. 
Note: this goes well with the Power of Now (a book). Power of Now = focus on now, don't think about problems. This has the effect of defocusing the left brain, and gives your right brain control

-change routine and habits frequently to shake up the mind

-when writing come up with new words frequently 

-uncertainty is a good thing. It leaves choices open. The opposite of this is committing to a big decision from the start, like putting a design decision in stone, discovering it's wrong, but continuing to use it and therefore build a castle on sand.

-cognitive biases. Biases cause us to reach false conclusions. There are many cognitive biases.

-at the beginning of a project you know the least, at the end you know the most. Therefore it's a bad idea to commit to ideas made at the beginning. Welcome change into the project, as more information becomes discovered.

-welcome and accept uncertainty. Deadlines are counterproductive. I can give estimates, and have a target date, but 100% certainty is not possible. This leads to making guarantees early which can't realistically get met, and in order to get close that's when 16 hour work days start to happen, and burnout happens. Innovation is surely stifled by this excessive working, and so you're probably introducing more bugs, and being less and less productive. It's better to have an estimated time, and a target date, but not set in stone. Don't worry about a deadline that was made without a proper estimate being given, that's totally unrealistic, and leads to the unproductive rush at the end to try to satisfy the unrealistic date. Instead, set a target date based on an estimate, and let others know that it's just an estimate. Others may panic and demand a deadline, but don't give in. Embrace uncertainty. Get it done, and get it done right.

-go back to reading many different things at once, instead of one at a time. I get very bored and don't read anything. Reading a wide variety of topics is also good for creativity,

-lead with intuition but follow up and verify with logic 

Learning 
-specific technologies are not important, whereas constantly learning is important

-goals need to be SMART. 
Specific 
Measurable: progress that is
Achievable: realistic 
Relevant: it must matter to me, and I must be in control of it
Time boxed: has a deadline

Pragmatic investment plan:
1. Concrete goals
2. Diversify: various technologies, languages, other. I already do this.
3. Active investment: this means check how my plan is going frequently. Example is doing the spanish quiz on duolingo. 
4. Make regular investment - this means make time for learning. Have a routine.

Believe in my intuition. Believe I can learn and do anything. Have humility, and an open mind. Be willing to fail.

-use "sq3r" or whatever when reading. That basically means to take notes and shit. Actively read, not just passively plowing through it. That is what I'm doing right now, taking notes 

-mind maps - take an idea and start drawing related ideas and how they relate. Do this by hand. Do this whenever brainstorming or at he start of a problem. Next time I get a support case or problem, mind map it
(Note on this note: I've been using mind maps alot, and they are freaking awesome. It basically allows you to quickly plan out what you're going to do, or brain storm, without writing long ass shit, and therefore more ideas plop out. It's a creative exercise.)

-don't waste time on documents that don't add value. If the design doc mirrors the code then don't do it. The design docs I do help me to plan out the steps to take and entities. It's where I put design questions, and put pictures of designs I came up with. I think this is practical, since I do it as I go along. It's sole purpose is to help me plan out the coding. The build docs help me to know what went into each build, this has proven useful over and over. Besides that, I don't think I do any other useless documents.

Documenting leads to insights, now or later
-preparation
-hand drawing
-creation of notes
Planning, and writing out the plan is the key. The actual doc is not important. This is like my design docs. They are basically repositories for my planning, and throwaway.

Do more creative documentation. Instead of long, boring documents, do a video, do screenshots 

Take notes by using mind maps. Use more pictures. This engages the right side brain more than text.

-playing with ideas should come before studying the facts

-accept "I don't know". Accept mistakes. The important part is to learn from those.

-experiment safely. Always be able to roll back changes, and test functionality. For example, have version control and unit testing  
(Note on this note: Typically when i'm trying to debug something i have a few ideas in mind. I try one, it doesn't work. Thank God for source control! I can just throwaway the changes and grab what's in the safe)

-during brainstorming accept all ideas. Allow failure. Don't shoot down ideas, becuse it hurts crewstovomg (sic) more ideas 

-imagining that you've done something is the same as actually doing it, according to the brain

-imagining you are the expert, performing something expertly, is enough to "grove" the brain, and increase your performance. Believing is powerful.

-embrace failure. Failure is OK. It's an effective way to learn

-focusing on one thing is key to getting things done. 
-meditation trains the brain to let go of the idle-loop chatter of the l-mode. And therefore meditation helps you to focus better in all areas of life

-use a personal wiki for organizing data, ideas, thoughts. This is kinda like my notes here and blog

-multitasking
(Note on this note: I didn't finish writing this, because I was distracted by something. That's kinda funny)

Email
-get out of the habit of constantly checking for email
-don't respond so fast to emails. The faster you respond, the more frequently you will get emailed. Control the tempo.
-IM is probably the worst distractions. Stay DND most of the time. Stay DND whenever I really need to focus, which is usually

Friday, April 3, 2015

Attack formations in Boom Beach

I switch up my armies depending on the enemy's base.

1. Defenses mostly away from the HQ - 100% warriors.

2. Defenses are heavily anti-infantry (anything that deals area damage), such as rocket launchers, shockers, flamethrowers - a mix of tanks and medics.

For example, this resource base has 7 rocket launchers and like 8 MGs. Tanks/medics had no problem with this.

3. Defenses are heavily anti-tank (anything that attacks 1 target a time), such as sniper towers and canons - swarm with heavies and riflemen. I like riflemen over zookas, because they are faster and therefore more maneuverable.

4. Defenses are a mix of anti-infantry and anti-tank, and the two types of defenses are "layered", so that anti-infantry or anti-tank is the first layer. - in this case i like a combo of tanks & rifles and medics. Depending on the first layer, send in either the tanks or rifles first.
For example, this resource base has a bunch of anti-infantry defenses up front, and then anti-tank defenses in the back. The best way to approach this is to send in the tanks first to clear out the anti-infantry defenses, then release the rifles. The rifles should then get in front of the tanks and swarm the anti-tank defenses. The key here is to swarm the defenses, and rifles are perfectly suited for this since they are medium range, cheap, and you can pack a whole bunch on an LC.
There was an error in this gadget