Dynamically Assign a Project Number

Hello,

I am relatively new and struggling on how to do something.  I am sure this will be of quick help.  I am creating a project management tool and the user will create a new project.  I want the system to dynamically assign the project number and ensure there are no duplicates.  I do not want to use the ID as the project number as our company has a specific format.  We use a two digit year plus an increment system of three digits.  For example this year it is 19001, 19002, 19003, etc.

My thought process was:

  1. Create a process in the business logic module that responds to the CreateProject action that Outsystems automatically creates for the entity.
  2. Use that to run an action that retrieves the last project number created (retrieves the list of projects with a maxcount of 1 sorted descending by project number
  3. I am thinking the project number needs to be an integer so I can add to it, so if I have 19001 I was trying to use substr on the project number to pull out the first two digits and compare it to the currdate.  I was getting a bit mixed up here as I need to convert the data types etc.
  4. When that is done I used an if statement to determine if the year matches.  If it does I just increment the project number (using a local variable).  If it does not I need to assign it to the new number and 001 after it.  Again getting tripped up here.....  Cause if the curr year is 19 how do I get the number to be 19001.
  5. Once I get through that the local variable has my proper project number and I nee to save that back to the new record.  Not sure how to do that either as this is running through a process.


So clearly not quite understanding exactly what to do from a "coding" perspective and possibly my approach isnt right at all.


Thoughts?

Hi! 

First ,  BPT is probably a little "hard" to your problem, perhaps create an action in your screen that after perform the work you describe calls the CreateProject action. Your new action should  be called instead of CreateProject action.

Second . "

  1.  retrieves the last project number created (retrieves the list of projects with a maxcount of 1 sorted descending by project number"  

What about a situation with tow or more users creating projects at the same time ? 

Hope to this helps your thoughts

Graça


Solution

Hi Ivan

For purposes of best practices, the use of a BPT to create this logic is not quite correct. It would work, but there is no need for a BPT. What I in your case would do is:

  1. Set the entity as Public, but Read-Only (assuming it is in a Core module)
  2.  Create a 'wrapper server action' that takes in an input of type 'Proyect Record' and returns an Output of type ProyectId
  3.  In that wrapper server action get the latest created proyect number. You can do it by as you said by Max(Proyect.Number)
  4. In the record that you're getting as in input, assign Proyect.Number = GetMaxProyectNumber.List.Current.MaxProyectNumer + 1
  5. Call the entity action CreateProyect (or CreateOrUpdateProyect). Assign as its input parameter, the ProyectRecord you received, which now has a the new proyect number assigned 
  6. Set your output parameter accordingly.

Every time you need to create a new proyect, instead of calling the Entity's CreateProyect, call this new wrapper action that you created. This way you have the business creation logic in one place, and can be sure that it will be applied as soon as you create a proyect, rather than in a BPT.

Please note that if you do set the Entity as Read-Only you'll have to create wrapper actions for all the entity actions that you need (Update, Delete, etc)


Hope this helps!

  CLSJ


Edit: Actually the Max(Proyect.Number) would not work. To get the appropiate proyect number you'd need to:

  1. Get the latest created Proyect, with a query sorted by Proyect.Id Desc (assuming the proyect Id is Integer and Autonumeric), limited to one record.
  2. NextProyectNumber = TextToInteger(Substr(IntegerToText(Year(CurrDate())),2,2) + Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),2,Length(GetLatestProyect.List.Current.Proyect.Number)-2))
Solution

Carlos López Santibáñez Jácome wrote:

Hi Ivan

For purposes of best practices, the use of a BPT to create this logic is not quite correct. It would work, but there is no need for a BPT. What I in your case would do is:

  1. Set the entity as Public, but Read-Only (assuming it is in a Core module)
  2.  Create a 'wrapper server action' that takes in an input of type 'Proyect Record' and returns an Output of type ProyectId
  3.  In that wrapper server action get the latest created proyect number. You can do it by as you said by Max(Proyect.Number)
  4. In the record that you're getting as in input, assign Proyect.Number = GetMaxProyectNumber.List.Current.MaxProyectNumer + 1
  5. Call the entity action CreateProyect (or CreateOrUpdateProyect). Assign as its input parameter, the ProyectRecord you received, which now has a the new proyect number assigned 
  6. Set your output parameter accordingly.

Every time you need to create a new proyect, instead of calling the Entity's CreateProyect, call this new wrapper action that you created. This way you have the business creation logic in one place, and can be sure that it will be applied as soon as you create a proyect, rather than in a BPT.

Please note that if you do set the Entity as Read-Only you'll have to create wrapper actions for all the entity actions that you need (Update, Delete, etc)


Hope this helps!

  CLSJ


Edit: Actually the Max(Proyect.Number) would not work. To get the appropiate proyect number you'd need to:

  1. Get the latest created Proyect, with a query sorted by Proyect.Id Desc (assuming the proyect Id is Integer and Autonumeric), limited to one record.
  2. NextProyectNumber = Substr(IntegerToText(Year(CurrDate())),2,2) + Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),2,Length(GetLatestProyect.List.Current.Proyect.Number)-2)


Carlos, so I guess a few questions....  What is the reasoning for read-only?  Is that to protect it to ensure I dont accidentally skip this workflow?  Am I creating the wrapper server action in the UI module or the service module?  I am assuming the service module as it would need writable access to the entity.

  • What is the reasoning for read-only?  Is that to protect it to ensure I dont accidentally skip this workflow?
    • Exactly
  • Am I creating the wrapper server action in the UI module or the service module
    • Since your Entity will be read-only, in the Services module, as you correctly stated


CLSJ


P.S. I think that my subst indexes in are wrong, as I believe Substr is zero-based. It should be:

  

  1. NextProyectNumber = Substr(IntegerToText(Year(CurrDate())),1,2) + Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),1,Length(GetLatestProyect.List.Current.Proyect.Number)-2)


Carlos, nevermind I "think" I got it!  Going to give it a whirl.


Thanks so much for your help, this was really insightful.

Happy to help. As for the year + 3 digits, that is what the expresion I included is for, let me break it down for you

Get the latest created Proyect, with a query sorted by Proyect.Id Desc (assuming the proyect Id is Integer and Autonumeric), limited to one record, I'm going to assume that aggregate is called GetLatestProyect.

NextProyectNumber 

TextToInteger(Substr(IntegerToText(Year(CurrDate())),1,2) + IntegerToText(TextToInteger(Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),1,Length(GetLatestProyect.List.Current.Proyect.Number)-2))+1))


  • Substr(IntegerToText(Year(CurrDate())),1,2) - This part of the expresion handles the last two digits of the current year
    1. CurrDate(): gets the current server date
    2. Year(): Get the year of the input date as an integer (2019 in this case)
    3. IntegerToText: Converts the year to text, so you can use text functions with it
    4. Substr: Gets a part of a string (19 in this case)


  • TextToInteger(Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),1,Length(GetLatestProyect.List.Current.Proyect.Number)-2))+1 - This part of the expresion handles adding one to the last 3 digits of your proyect
    1. IntegerToText(GetLatestProyect.List.Current.Proyect.Number): Convert the last created proyect number to text
    2. Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),1,Length(GetLatestProyect.List.Current.Proyect.Number)-2): Do a substring of that to get the number without the first two digits
    3. TextToInteger(Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),1,Length(GetLatestProyect.List.Current.Proyect.Number)-2))+1 - Convert that text to a number again and add 1
    4. IntegerToText(TextToInteger(Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),1,Length(GetLatestProyect.List.Current.Proyect.Number)-2))+1)) - Convert the number + 1 to text again so you can concatenate the year with the number as a string

And finally, convert the Year+Number from text to integer, so you can store it in the Db as an int.


I hope that is clear enough. It's a bit of a long expresion, but should work


   CLSJ

Carlos López Santibáñez Jácome wrote:

Happy to help. As for the year + 3 digits, that is what the expresion I included is for, let me break it down for you

Get the latest created Proyect, with a query sorted by Proyect.Id Desc (assuming the proyect Id is Integer and Autonumeric), limited to one record, I'm going to assume that aggregate is called GetLatestProyect.

NextProyectNumber 

TextToInteger(Substr(IntegerToText(Year(CurrDate())),1,2) + IntegerToText(TextToInteger(Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),1,Length(GetLatestProyect.List.Current.Proyect.Number)-2))+1))


  • Substr(IntegerToText(Year(CurrDate())),1,2) - This part of the expresion handles the last two digits of the current year
    1. CurrDate(): gets the current server date
    2. Year(): Get the year of the input date as an integer (2019 in this case)
    3. IntegerToText: Converts the year to text, so you can use text functions with it
    4. Substr: Gets a part of a string (19 in this case)


  • TextToInteger(Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),1,Length(GetLatestProyect.List.Current.Proyect.Number)-2))+1 - This part of the expresion handles adding one to the last 3 digits of your proyect
    1. IntegerToText(GetLatestProyect.List.Current.Proyect.Number): Convert the last created proyect number to text
    2. Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),1,Length(GetLatestProyect.List.Current.Proyect.Number)-2): Do a substring of that to get the number without the first two digits
    3. TextToInteger(Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),1,Length(GetLatestProyect.List.Current.Proyect.Number)-2))+1 - Convert that text to a number again and add 1
    4. IntegerToText(TextToInteger(Substr(IntegerToText(GetLatestProyect.List.Current.Proyect.Number),1,Length(GetLatestProyect.List.Current.Proyect.Number)-2))+1)) - Convert the number + 1 to text again so you can concatenate the year with the number as a string

And finally, convert the Year+Number from text to integer, so you can store it in the Db as an int.


I hope that is clear enough. It's a bit of a long expresion, but should work


   CLSJ

Your explanation was awesome, so appreciated.  Good news is after I typed that I had figured it out after re-reading one of your posts so I actually did figure out on my own which makes me feel good as means I was processing it :)


Thanks again!