Search This Blog

Wednesday, May 29, 2013

SugarCRM relationship names.

Programming with beans, in Sugar, you would use code like this to create a note and then attach it to a related entity - in this case an 'Invoice'.

$note = new Note();
$note->modified_user_id = $current_user->id;
$note->name='invoiced on ' . date('d/m/Y');
$note->description='next due: '.$bean->next_due.'; rate: '.$bean->rate;
$note->save();

$rel_name = 'inv_invoices_notes_1';
        $bean->load_relationship($rel_name);
$bean->$rel_name->add($note->id, array());

$bean->save();  
However you need to know the name of the relationship - in this case inv_invoices_notes_1, .
This relationship was created in Studio between 2 already existing modules.
You can get the relationship name from a php file called vardefs.ext.php - in custom/module/<module-name>/Ext/Vardefs/:

// created: 2013-04-18 06:00:38
$dictionary["INV_Invoices"]["fields"]["accounts_inv_invoices_1"] = array (
  'name' => 'accounts_inv_invoices_1',
  'type' => 'link',
  'relationship' => 'accounts_inv_invoices_1',
  'source' => 'non-db',
  'vname' => 'LBL_ACCOUNTS_INV_INVOICES_1_FROM_ACCOUNTS_TITLE',
  'id_name' => 'accounts_inv_invoices_1accounts_ida',
);



Thursday, May 16, 2013

MS CRM - Hide a form element based on the Users' Role

Often if testing a new element in CRM - for example an external link, you will only want it visible to certain users.
This may be the case even after that particular item is complete.
You can use javascript to do this, by querying for the user roles in Xrm.Page.context.getUserRoles():


function Form_onload()
{

    // check for test Role: 7e15636f-ab5f-e211-96b4-000c29f98cb8
    var testRoleId = '7e15636f-ab5f-e211-96b4-000c29f98cb8';
    var userRoles =Xrm.Page.context.getUserRoles();

    //test code  list user roles to console
    for (var i=0; i < userRoles.length; i++) {
        window.console &&  window.console.log(i + ' role:' + userRoles[i]);
    }

    var inTestRole =  userRoles.indexOf(testRoleId);
    if ( inTestRole < 0 ) {
        var navPriceListItem = document.getElementById('navLink{814d1cba-5412-2bc0-a7ae-46bc80384583}');
        navPriceListItem.style.display = 'none';
    }
}

Ideally you should be able to query the roles by name and also identify the form element better.


Friday, May 10, 2013

JavaScript dataAdd function

One of the few gifts to programming contributed by the BASIC language is the dateAdd function.
I have seen versions of it ported to Oracle PL/SQL, SQL Server TSQL, PHP and other languages.
Javascript is an obvious candidate for this.
Like many languages, Javascript has a date object and you can easily add a required number of days to it, (this is how date additions are handled in Oracle SQL).
However what you want is to be able to easily and reliably add different periods - weeks, months, years a date add function specifying the interval.
There are various examples - here, and here and even here, the problem with these is that they don't add months correctly.
If you add 1 month to the 31st of January - e.g.
var d = new Date ('31/01/2007');
d.dateAdd('m', 1);
The code suggested is:

 case "m":
      dTemp.setMonth(dTemp.getMonth() + iNum);
      break;


This is logical but as January has 31 days and February has (usually) 28, it appears the setMonth function increments the date by the length of the current month.
I had to implement an invoicing function, and accounts people tend to be very particular about their dates.
My suggestion for a month add algorithm is:

case "m": {        // month
    var dParts = { 'year' : this.getFullYear
                 , 'month': this.getMonth()
                 , 'day' : this.getDate() };
    dParts.month =+ p_Number;
    if (dParts.month > 11) {
   dParts.month =- 11;
   dParts.year++;
        this.setYear(dParts.year);
    }
    this.setMonth(dParts.month);
    break;
}

The logic handles wrapping around a year by extracting the month and year (also the day which I didn't think I needed).

This is better, but still not correct. When adding one month to 31st of January, we will get the 3rd of March. This is because the new day is 31st of February, which is then wraps over into March.
We need to know how many days are in the next month and adjust accordingly.



        case "m": {        // month
            var dParts = {'year' : this.getFullYear()
                          , 'month': this.getMonth()
                          , 'day' : this.getDate() };
            dParts.month = dParts.month + p_Number;
            if (dParts.month > 11) {
             dParts.month = dParts.month - 11;
             dParts.year++;
             this.setYear(dParts.year);
            }
            newMonthLength = daysInMonth(dParts.month, dParts.year);
            if ( dParts.day > newMonthLength) { this.setDate(newMonthLength); }
            this.setMonth(dParts.month);
            break;
                    }



I wrote 2 functions - one as an extension to the date class and a standalone function that just takes the month and year.


function daysInMonth_Extension() {
var year = this.getFullYear();
var month = this.getMonth();
if (month == 1) {
//february
if ( year % 500 == 0) { return(29); }
else if (year % 100 == 0) { return(28); }
else if (year % 4 == 0) { return(29); }
else { return(28) };
} else if (month == 3 || month == 5 || month == 8 || month == 10) { return(30); }
else {return(31); } 
}  /* daysInMonth_Extension */
Date.prototype.daysInMonth = daysInMonth_Extension;

function daysInMonth(month, year) {
if (month == 1) {
//february
if ( year % 500 == 0) { return(29); }
else if (year % 100 == 0) { return(28); }
else if (year % 4 == 0) { return(29); }
else { return(28) };
} else if (month == 3 || month == 5 || month == 8 || month == 10) { return(30); }
else {return(31); } 
}  /* daysInMonth */


This allows for February and September, April, June and November, remember month numbers start at 0. I should/will refactor this, but by now I was getting over it.


Incorporating code from the first link above you get the following - which I will put up on gitHub:






function daysInMonth_Extension() {
var year = this.getFullYear();
var month = this.getMonth();
if (month == 1) {
//february
if ( year % 500 == 0) { return(29); }
else if (year % 100 == 0) { return(28); }
else if (year % 4 == 0) { return(29); }
else { return(28) };
} else if (month == 3 || month == 5 || month == 8 || month == 10) { return(30); }
else {return(31); } 
}  /* daysInMonth_Extension */
Date.prototype.daysInMonth = daysInMonth_Extension;

function daysInMonth(month, year) {
if (month == 1) {
//february
if ( year % 500 == 0) { return(29); }
else if (year % 100 == 0) { return(28); }
else if (year % 4 == 0) { return(29); }
else { return(28) };
} else if (month == 3 || month == 5 || month == 8 || month == 10) { return(30); }
else {return(31); } 
}  /* daysInMonth */

function myDateAdd_Extension(p_Interval, p_Number){
    var thing = new String();
   
    //in the spirt of VB we'll make this function non-case sensitive
    //and convert the charcters for the coder.
    p_Interval = p_Interval.toLowerCase();
    
    if(isNaN(p_Number)){
    
        //Only accepts numbers 
        //throws an error so that the coder can see why he effed up    
        throw "The second parameter must be a number. \n You passed: " + p_Number;
        return false;
    }
    p_Number = new Number(p_Number);
    switch(p_Interval.toLowerCase()){
        case "yyyy": {// year
            this.setFullYear(this.getFullYear() + p_Number);
            break;
        }
        case "q": {        // quarter
            this.setMonth(this.getMonth() + (p_Number*3));
            break;
        }
        case "m": {        // month
            var dParts = { 'year' : this.getFullYear(), 'month': this.getMonth(), 'day' : this.getDate() };
            dParts.month = dParts.month + p_Number;
            if (dParts.month > 11) {
            dParts.month = dParts.month - 11;
            dParts.year++;
            this.setYear(dParts.year);
            }
            newMonthLength = daysInMonth(dParts.month, dParts.year);
            if ( dParts.day > newMonthLength) { dParts.day = newMonthLength; this.setDate(newMonthLength); }
            this.setMonth(dParts.month);
            break;
                    }
        case "y":        // day of year
        case "d":        // day
        case "w": {        // weekday
            this.setDate(this.getDate() + p_Number);
            break;
        }
        case "ww": {    // week of year
            this.setDate(this.getDate() + (p_Number*7));
            break;
        }
        case "h": {        // hour
            this.setHours(this.getHours() + p_Number);
            break;
        }
        case "n": {        // minute
            this.setMinutes(this.getMinutes() + p_Number);
            break;
        }
        case "s": {        // second
            this.setSeconds(this.getSeconds() + p_Number);
            break;
        }
        case "ms": {        // second
            this.setMilliseconds(this.getMilliseconds() + p_Number);
            break;
        }
        default: {
        
            //throws an error so that the coder can see why he effed up and
            //a list of elegible letters.
            throw    "The first parameter must be a string from this list: \n" +
                    "yyyy, q, m, y, d, w, ww, h, n, s, or ms. You passed: " + p_Interval;
            return false;
        }
    }
    return this;
}
Date.prototype.dateAdd = myDateAdd_Extension;

Note - I have tested this with days, years and months, for example:


            var dToday = new Date();    
            alert(dToday.dateAdd("m", 2));


However I haven't tested it with hours, minutes, seconds and milliseconds.

Subsequent Testing.

This may be better, but it still doesn't handle the rollover correctly.
If you add 14 months to a date, or addings day rolls over a month or year.

One easy change, change the if condition in month add code to a while statement:

        case "m": {        // month
            var dParts = { 'year' : this.getFullYear(), 'month': this.getMonth(), 'day' : this.getDate() };
            dParts.month = dParts.month + p_Number;
            while (dParts.month > 11) {
             dParts.month = dParts.month - 11;
             dParts.year++;
             this.setYear(dParts.year);
            }
            newMonthLength = daysInMonth(dParts.month, dParts.year);
            if ( dParts.day > newMonthLength) { dParts.day = newMonthLength; this.setDate(newMonthLength); }
            this.setMonth(dParts.month);


Adding days has the same problem, so I refactored the code adding addDays and addMonths functions:


        case "m": {        // month
            var dParts = { 'year' : this.getFullYear(), 'month': this.getMonth(), 'day' : this.getDate() };
            this.addMonths(dParts, p_Number);
        }
        case "d" : {
            var dParts = { 'year' : this.getFullYear(), 'month': this.getMonth(), 'day' : this.getDate() };
            this.addDays(dParts, p_Number);
        }




function myDateAdd_addMonths_Extension(dParts, p_Number){
            dParts.month = dParts.month + p_Number;
            while (dParts.month > 11) {
             dParts.month = dParts.month - 11;
             dParts.year++;
             this.setYear(dParts.year);
            }
            newMonthLength = daysInMonth(dParts.month, dParts.year);
            if ( dParts.day > newMonthLength) { dParts.day = newMonthLength; this.setDate(newMonthLength); }
            this.setMonth(dParts.month);
}


function myDateAdd_addDays_Extension(dParts, p_Number){
            dateParts.day += p_Number;
            monthLength = daysInMonth(dParts.month, dParts.year);
    while ($dateParts->d > $monthLength) {
dParts.month++;
dateParts.day -= monthLength;
if (dateParts.month > 11) {
             dParts.month = dParts.month - 11;
             dParts.year++;
         } }
               monthLength = daysInMonth(dParts.month, dParts.year);
    }

}


Date.prototype.addMonthsmyDateAdd_addMonths_Extension;
Date.prototype.addDaysmyDateAdd_addDays_Extension;


This shows how complicated date calculations are, more testing is need to verify this code.

Configuring a XenServer 6/FreeNAS 8.2.0 Environment

These 2 versions of software - XenServer 6 on a whitebox PC and FreeNAS on an old Dell Poweredge 860 present some interesting configuration issue.
My goal was to configure a test environment before making changes to a production system and verify:

  • Configuring an intial system - with the XenServer and Centos 5.8 guest to access the NAS.
  • The process of adding Network Adaptors to the XenServer.
  • Configuring a storage local area network for the XenServer(s) to access the NAS.
  • Moving the NAS from the management network (Network 0) to the storage network.
  • Ensuring the guest VMs are still operational and can access their data.

XenServer 6.0 Installation and VM Configuration.

This was a straightforward installation. the XenServer was given a fixed IP. Version 6.0 build 50762p was installed.
 Creation of the VMs - with Centos 5.8 required using the generic "Other install media" templates. Using either the Centos or RedHat EL templates caused various errors - either at install - no mass storage devices found, or after install and errors like:
 File "/usr/bin/pygrub", line 808, in ?, fs = fsimage.open(file, part_offs[0], bootfsoptions), IOError: [Errno 95] Operation not supported

Once the generic template was used the VM could be installed and created as expected.


FreeNas 8.6.2 Installation on Dell PowerEdge 860.

FreeNas has the requirement that the drive the OS is installed on cannot be used for sharing, the installer reccomends use of a flash drive for the OS.
To add an boot of a flash drive, you need to modify the BIOS configuration for the 860, specifically:
1) Change the USB Flash Drive Emulation Type to "Hard Disk"
2) Change the order of the Hard Disk drive Sequence to make sure the USB drive is first.
The blog post at SysAdmin Notes is very  helpful.
I enabled SATA drives - rather than the PERC controller so FreeNas could access the drives.
I then followed the Quick Start Guide

  • set admin and root account password.
  • configure the network interface, by default the system will boot using DHCP on the first interface.
  • enabled display of console messages in footer.
  • created a volume on the SATA drive  (/mnt/ada0).
  • created a zfs volume (ada01) with in the ada0 volume 
  • manually create a user account (as opposed to using LDAP or Active Directory).
  • perform ISCSI configuration (see below).

ISCSI Configuration.

I did these steps:
  • Enabled the iSCSI service - under services.
  • Created a iSCSI authorized access. If using the Microsoft iSCSI initiator, the CHAP password should be between 12 and 16 characters long I would make it 12 characters long.
  • Created device extent on ada01
  • Create an initiator - set of systems allowed to connect, as per the quick start - ALL addresses and connect to ALL intitiators.
  • Specify portal - IP address and port to be used for iSCSI connections.
  • Check the Target Global Configuration - set Enable LUC to on, and Controller Auth Method to None, ensure Controller IP Address and Controller Network are the defaults. This will let you dynamically add/remove targets without having to restart the iSCSI service.
  • Create a target. Specify the name, alias Portal Group ID, Initiator Group ID, Auth Method and Authentication Group number. I set the Auth Method to CHAP. For a new system where will only be one Portal Group, Initiator Group and Authentication group.
  • Associated the Target with an extent. Select the Target and Extents created above - for a new system there will be only one selection.


MS CRM Field, Entity Reference.

This post is a collection of example sql squeries and other information on the entities underlying Microsoft's CRM. The version in particular is 2011, however much would be relevant for previous versions (e.e. V4).

Notes Associated with an Entity

The notes - entitiy name is Annotations have an ObjectID field which maps to the ID field of the 'parent' record, for an esample with Cases (which have a database entity of 'Incidents'), the join query would be:


select i.ticketnumber , i.description, note.notetext
from dbo.Incident  i
inner join dbo.Annotation note on i.IncidentId = note.ObjectId
where i.TicketNumber = 'JOB-24944-P8R3'
order by note.CreatedOn desc