Buscar en este blog

martes, 17 de febrero de 2015

Crear empleado, alta y baja por código x++ en Dynamics Ax 2012


Para crear un empleado por código en Ax necesitamos varias líneas, el siguiente código es útil cuando los empleados no se necesitan crear con dimensiones financieras. La primera parte es la creación, posteriormente agrego la forma de cómo usarlo en un job, que bien pudiera ser la salida de una lectura de un archivo de excel y finalmente el código cuando es necesario darlo de baja.

public void creaEmpleado(FirstName _nombre, MiddleName _segundoNombre, LastName _apellidos,
                         HcmPersonnelNumberId _ClaveTrabajador, Date _fechaIngreso,
                         Date _fechaBaja, Email _mail, HcmPositionId _positionId,
                         str _tipoAccion)
{
    HcmWorker               HcmWorker;
    dirPerson               dirPerson;
    dirPersonName           dirPersonName;
    HcmEmployment           HcmEmployment;
    HcmTitle                HcmTitle;
    HcmPersonnelNumberId    PersonnelNumber;
    Name                    firstName, middleName, lastName, name;
    dirPartyTable           dirPartyTable;
    HcmActionState          hcmActionState;
    HRMParameters           hcmParameters;
    HcmActionTypeSetup      hcmActionTypeSetup;
 
    LogisticsLocation LogisticsLocationElectronic;
    LogisticsElectronicAddress  LogisticsElectronicAddress;
    utcDateTime     fechaIngreso, fechaBaja;
    str             vProfessionalTitle;
    str             strEmail;
    ;
 
    try
    {
        hcmParameters = HRMParameters::find();

        PersonnelNumber = _ClaveTrabajador;
 
        ttsBegin;
        HcmWorker = hcmWorker::findByPersonnelNumber(PersonnelNumber, true);

        if(!HcmWorker)
            hcmWorker.PersonnelNumber = PersonnelNumber;
 
        firstName = strLRTrim(_nombre);
        middleName = strLRTrim(_segundoNombre);
        lastName  = strLRTrim(_apellidos);
        name  = firstName + " " + middleName + " " + lastName;

        //ajustando fechas
        if (_fechaIngreso)
            fechaIngreso = DateTimeUtil::newDateTime(_fechaIngreso, 0, DateTimeUtil::getCompanyTimeZone());
        else
            fechaIngreso = DateTimeUtil::minValue();
 
        if (_fechaBaja)
            fechaBaja = DateTimeUtil::newDateTime(_fechaBaja, 0, DateTimeUtil::getCompanyTimeZone());
        else
            fechaBaja = DateTimeUtil::maxValue();
 
        if (HcmWorker.Person)
        {
            dirPartyTable = dirPartyTable::find(dirPerson::find(HcmWorker.Person).PartyNumber);
            if (!dirPartyTable)
            {
                DirPartyTable = DirPartyTable::grwcreateNew(name, firstName, middleName, lastName);
            }
            else
            {
                dirPersonName   = DirPersonName::find(HcmWorker.Person, true);
                dirPersonName.ValidFrom = fechaIngreso;
                dirPersonName.ValidTo = fechaBaja;
                dirPersonName.validTimeStateUpdateMode(ValidTimeStateUpdate::Correction);
                dirPersonName.FirstName     = firstName;
                dirPersonName.MiddleName    = middleName;
                dirPersonName.LastName      = lastName;
                dirPersonName.write();
            }
        }
        else
        {
            DirPartyTable = DirPartyTable::grwcreateNew(name, firstName, middleName, lastName);
        }
 
        dirPerson = dirPerson::find(DirPartyTable.RecId, true);
 
        dirPerson.ProfessionalTitle = this.getPosition(strLRTrim(_positionId)).PositionId;
        vProfessionalTitle          = dirPerson.ProfessionalTitle;
        dirPerson.NameSequence      = DirNameSequence::find( "FirstMiddleLast").RecId;
        dirPerson.LanguageId        = "es-mx";
 
        dirPerson.write();
 
        HcmWorker.Person = dirPerson.RecId;
 
        HcmWorker.write();
 
        if(!HcmTitle::findByTitle(dirPerson.ProfessionalTitle))
        {
            HcmTitle.TitleId    = dirPerson.ProfessionalTitle;
            HcmTitle.insert();
        }
 
        HcmEmployment = HcmEmployment::findByWorkerLegalEntity(HcmWorker.RecId, CompanyInfo::find().RecId, DateTimeUtil::minValue(), DateTimeUtil::maxValue(), true);
 
        if(!HcmEmployment)
        {
            HcmEmployment.LegalEntity    = CompanyInfo::find().RecId;
            HcmEmployment.Worker         = HcmWorker.RecId;

            HcmEmployment.ValidFrom = fechaIngreso;
            HcmEmployment.ValidTo = fechaBaja;
        }
 
        HcmEmployment.EmploymentType = HcmEmploymentType::Employee;
 
        if(!HcmEmployment)
            HcmEmployment.write();
        else
            HcmWorkerTransition::newUpdateHcmEmployment(hcmEmployment, HcmEmployment.ValidFrom, HcmEmployment.ValidTo);

        /*//Estas líneas sirven en caso de que se requiera guardar registro de cada actividad
        //sobre los empleados, ya sea contrataciones, bajas, cambios de puesto
        select firstonly hcmActionTypeSetup
        where hcmActionTypeSetup.Name == hcmParameters.grwAltaEmpleado &&
              hcmActionTypeSetup.ActionType == HcmActionType::Hire;

        hcmActionState.clear();
        hcmActionState.Originator = hcmWorker.RecId;
        hcmActionState.ActionTypeSetup = hcmActionTypeSetup.RecId;
        hcmActionState.ApprovalStatus = HcmApprovalStatus::Completed;
        hcmActionState.insert();*/

        strEmail    = _mail;

        if (strEmail)
        {
            LogisticsLocationElectronic = LogisticsLocation::create( "Correo electrónico", NoYes::No);
            LogisticsElectronicAddress.clear();
            LogisticsElectronicAddress.Location = LogisticsLocationElectronic.RecId;
            LogisticsElectronicAddress.Description = name;
            LogisticsElectronicAddress.Type     = LogisticsElectronicAddressMethodType::Email;
            LogisticsElectronicAddress.Locator  = strEmail;
            LogisticsElectronicAddress.IsPrimary = NoYes::Yes;
            LogisticsElectronicAddress.insert();
 
            DirParty::addLocation(dirPerson.RecId, LogisticsLocationElectronic.RecId, false, true);
 
            DirPartyTable = DirPartyTable::find(DirPartyTable.PartyNumber, true);
            DirPartyTable.PrimaryContactEmail = LogisticsElectronicAddress.RecId;
            DirPartyTable.doUpdate();
        }

        ttsCommit;

    }
    catch (exception::Error)
    {
        error( "Proceso cancelado");
        ttsabort;
    }
}  

La llamada mediante job quedaría:

La llamada quedaría:

static void llamada(Args _args)
{
    date ingreso, fin;
    GRW_ServiceOperationEmpleados clase = new GRW_ServiceOperationEmpleados();

    ingreso = 01\01\2015;
    fin = 09\02\2016;

    clase.creaEmpleado( "Jose", "Luis" , "Lopez", "4300009", ingreso, fin, "joselopez@gmail.com" , "123", "ALTA MANUAL" );

    info( "termino");
}

Y en Ax, en el módulo de recursos humanos veríamos esto:



Baja de empleados


La baja de empleados básicamente es decirle a Ax la fecha de termino de actividades de empleado, es decir, hasta cuando son válidos los datos de ese empleado en particular y aquí debemos tomar en cuenta dos aspectos, el empleado y los datos de la persona, que en este caso son los mismos.
Entonces, basta con modificar la fecha de HcmEmployment y de DirPersonName, solo que debemos considerar ciertos puntos que se muestran en el código porque estas tablas tienen la propiedad de ValidTimeStateFieldType; este método también lo uso para volver a reactiar/contratar un empleado.

private void bajaEmpleado(FirstName _nombre, MiddleName _segundoNombre, LastName _apellidos,
                          HcmPersonnelNumberId _ClaveTrabajador, Date _fechaIngreso,
                          Date _fechaBaja, Email _mail, HcmPositionId _positionId,
                          str _tipoAccion)
{
    HcmEmployment           hcmEmployment;
    HcmWorker               hcmWorker;
    utcDateTime             fechaBaja, fechaUTCMin, fechaUTCMax;
    DirPersonName           dirPersonName;

    if (_fechaBaja)
        fechaBaja = DateTimeUtil::newDateTime(_fechaBaja, 0, DateTimeUtil::getCompanyTimeZone());
    else
        fechaBaja = DateTimeUtil::maxValue();

    ttsBegin;
    hcmWorker = HcmWorker::findByPersonnelNumber(_ClaveTrabajador);
   
    fechaUTCMin = DateTimeUtil::minValue();
    fechaUTCMax = DateTimeUtil::maxValue();
   
    //Para modificar la fecha de alta o baja de un empleado se deben modificar
    //dos tablas HcmEmploymetn y DirPersonName que son las tablas que tienen
    //fecha de validez para el empledo
    while select forUpdate firstOnly validTimeState(fechaUTCMin, fechaUTCMax)
                 hcmEmployment where HcmEmployment.Worker == hcmWorker.RecId
    {
        if(hcmEmployment)
        {
            //Cuando se hace modificación a fechas de una tabla con la propiedad ValidTimeStateFieldType
            //se debe indicar que se va a realizar una corrección a la fecha, para eso es la
            //siguiente instrucción
            hcmEmployment.validTimeStateUpdateMode(ValidTimeStateUpdate::Correction);
            hcmEmployment.ValidTo = fechaBaja;
            hcmEmployment.update();
        }
    }
   
    //validTimeState se debe poner para que podamos seleccionar los registros que
    //no son validos para la fecha actual, si solo ponemos un select normal
    //no nos va a regresar nada la consulta
    while select forUpdate firstOnly validTimeState(fechaUTCMin, fechaUTCMax)
                 dirPersonName where dirPersonName.Person == hcmWorker.Person
    {
        if(dirPersonName)
        {
            dirPersonName.validTimeStateUpdateMode(ValidTimeStateUpdate::Correction);
            dirPersonName.ValidTo = fechaBaja;
            dirPersonName.update();
        }
    }
   
    ttsCommit;
}


Y por cierto, acuerdate de darle click a algún anuncio si el post te sirvio de algo.


No olvides que te puedes unir a la página en Facebook Aprendiendo Dynamics Ax donde únicamente se tratan temas de desarrollo y se busca crear una comunidad de desarrollador@s de Ax en nuestro idioma. 


lunes, 16 de febrero de 2015

Lista de objetos que pertenecen a un proyecto de Dynamics Ax 2012 R2

Este es un pedazo de código que sirve para leer los nombres de todos los objetos de un proyecto en Ax.

static void listAllObjectosFromProject(Args _args)
{
  ProjName        nombreProyecto = "LCProjectSearch_AX2012_R1" ;

  ProjectListNode   list = infolog.projectRootNode().AOTfindChild("Shared");

  TreeNodeIterator  ir = list.AOTiterator();
  ProjectNode      pnProj;
  ProjectNode      pn = list.AOTfindChild(nombreProyecto);

  void searchAllObj(projectNode rootNode)
  {
    #TreeNodeSysNodeType

    TreeNode          childNode;
    TreeNodeIterator      rootNodeIterator;
    ;

    if (rootNode)
    {
      rootNodeIterator = rootNode.AOTiterator();
      childNode = rootNodeIterator.next();
      while (childnode)
      {
        if (childnode.treeNodeType().id() == #NT_PROJECT_GROUP)
        {
          searchAllObj(childNode);
        }
        else
        {
            info( strfmt ("%1|%2" , childNode.AOTname(),rootNode.AOTname()));
        }
        childNode = rootNodeIterator.next();
      }
    }
  }
  ;

  if (pn)
  {
    info( strFmt ("PROYECTO: %1 " , nombreProyecto));
    pnProj = pn.loadForInspection();
    searchAllObj(pnProj);
    pnproj.treeNodeRelease();
  }

}
  
El resultado es como el siguiente:



También existe un proyecto que nos comparte Loncar Technologies que funciona para buscar cualquier objeto de ax y saber a cuáles proyectos pertenece, desde aquí se descarga el proyecto para ax 4.0, ax 2009 y ax 2012.




Y por cierto, acuérdate de darle click a algún anuncio si el post te sirvió de algo.

No olvides que te puedes unir a la página en Facebook Aprendiendo Dynamics Ax donde únicamente se tratan temas de desarrollo y se busca crear una comunidad de desarrollador@s de Ax en nuestro idioma. 


miércoles, 11 de febrero de 2015

Consumir servicio personalizado AIF con credenciales – Custom Services, Microsoft Dynamics AX 2012

En el post anterior se vio cómo exponer un servicio desde Ax y consumirlo desde visual studio estando en la misma red y con un usuario de dominio.

Pero existen los casos que son los mas comunes, donde nos preguntamos:

Qué pasa si por ejemplo nos conectamos por vpn a donde se encuentra el servicio expuesto de ax?
Qué pasa si nuestra máquina no esta dentro del dominio?

Dando por sentado que la tecnología desde la que se va invocar el servicio es .Net (o sea visual studio) el siguiente método funciona bien, por qué? porque el servicio que expone Ax por default es NetTCP, si se va a consumir desde otra tecnología o fuera de la red sin vpn, necesita ser http.

1. Verificar que tenemos acceso al servicio expuesto desde nuestra red, esto lo hacemos abriendo el explorador y poniendo la url del servicio que creamos.


Si lo vemos en el explorador, quiere decir que si alcanzamos el servicio.

2. Crear un usuario de active directory y usuario de ax. Este usuario nos va a servir para firmar nuestro usuario y accesar al servicio.


3. Crear un proyecto de visual studio de tipo windows form (aquí el tipo no importa, solo es para probar la invocación) y agregamos la referencia del servicio. Agregamos un botón al proyecto y sobre el evento click del botón invocamos el servicio, la diferencia con el ejemplo anterior es que se necesita firmar con las credenciales de un usuario, en este caso el usuario que creamos en el paso anterior. El código sería:

private void button1_Click( object sender, EventArgs e)
{
     try
     {
          using (Servicio2NetTCPEmpleados. GRWEmpleadosAxClient servicioWCF = newServicio2NetTCPEmpleados.GRWEmpleadosAxClient ())
          {
               Servicio2NetTCPEmpleados. CallContext contexto = new Servicio2NetTCPEmpleados.CallContext ();
               contexto.Company = "compañiaAx" ;
               contexto.MessageId = Guid.NewGuid().ToString();
               servicioWCF.ClientCredentials.Windows.ClientCredential.Domain = "dominioDeServidor" ;
               servicioWCF.ClientCredentials.Windows.ClientCredential.UserName = "usuario" ;
               servicioWCF.ClientCredentials.Windows.ClientCredential.Password = "contraseña" ;

               //Llamada al servicio
               string respuesta = servicioWCF.metodoServicio(contexto, "parametro1" "parametro2");

               MessageBox .Show(respuesta);
          }
     }
     catch ( Exception ex)
     {
          MessageBox .Show(ex.Message);
     }
}

Algo importante por mencionar, es que el dominio, debe ser el que dice la maquina donde se esta exponiendo el servicio sobre ax, por ejemplo:


Sino se hace de esta forma, al invocar el servicio, el error que veríamos seria: "El objeto de comunicación, Systema.ServiceModel.Channels.ServiceChannel, no se puede usar para la comunicación porque se encuentra en el estado Faulted."




Y por cierto, acuérdate de darle click a algún anuncio si el post te sirvió de algo.

No olvides que te puedes unir a la página en Facebook Aprendiendo Dynamics Ax donde únicamente se tratan temas de desarrollo y se busca crear una comunidad de desarrollador@s de Ax en nuestro idioma. 


<<<<----   Crear servicio personalizado AIF                            Publicar servicio como HTTP  --->>>