Hi All, question please, is it possible in Creatio to :
1. Change Text color field automatically from business process

2. Or make font from normal to Bold automatically from business process

If it can't, maybe any method to make it happen?

I googling, found this :

I just want to confirm, or maybe you have workaround to do this?

The scenario is :

I have 4 data model, and each from the column is the same field.

I want to compare all, and if we find the different value between them, it will change the color to red, OR if not possible, the workaround is make the font Bold.

So the user can aware, and fast directly notice the different.

Thank you.

Like 0

Like

1 comments

Hi,

A business process only writes data. It cannot change color or font weight on the page. But the page can change its own styling based on data, so the pattern is: the process writes a "mismatch" flag, and the page reads that flag to style the field.

Step 1. Store the comparison result in a column

On the object shown on the page, add one column per compared field, for example UsrDobMismatch (Boolean). Optionally add a text column UsrDobColor that holds either auto or a hex color like #d32f2f.

The business process reads the four records (Read data x4), compares them with a Formula element, and writes the result with Modify data. Trigger it on record save or on a schedule, whatever fits your flow.

Step 2. Style the page from that column

Option A, no code at all. In the Freedom UI page designer, add two copies of the value element for each field. Make one plain and one bold with red color (the Label element has color and thickness settings in the designer). Then add page business rules: show the red element when UsrDobMismatch is true, show the plain one when it is false.

Option B, small code change. Show the value as a crt.Label instead of an input and bind the styling properties to attributes. Label properties support $Attribute binding, so if the process fills the color column, no handler is needed:

{
  operation: "insert",
  name: "SurveyDobLabel",
  values: {
    type: "crt.Label",
    caption: "$PDS_UsrSurveyDob",
    labelType: "body",
    labelColor: "$PDS_UsrDobColor",        // "auto" or "#d32f2f"
    labelThickness: "$PDS_UsrDobThickness" // "default" or "semibold"
  },
  parentName: "SurveyColumn",
  propertyName: "items",
  index: 1
}

If you prefer not to store styling values in the database, keep only the Boolean column and compute the color in a crt.HandleViewModelAttributeChangeRequest handler on the page. When the Boolean attribute changes, set a DobColor attribute to red or auto and bind labelColor to it.

One thing to watch: if the process runs after save, the page will show the new styling only after reload. If the users expect it instantly, do the comparison in the page handler instead of the process. The four values are already loaded on the page, so the handler can compare them directly and set the color attributes with no round trip.

Show all comments

Hello community,

the 10x version enable the list-base time selection for "Time" and "Date/Time" type fields.

Do you know how I can enable it?

 

Like 1

Like

1 comments

Hi,

I found, enable the feature "EnableDateTimeCalendarListPickerView"..

  1. Open the Feature toggling page
    Enter {CreatioURL}/0/Flags in the browser address bar, for example https://mycreatio.com/0/Flags. This is valid for Creatio 8.x and later.
    You need an administrator account.

  2. Find the feature
    Search the list for EnableDateTimeCalendarListPickerView (the Code column). The grid is long, so use the browser's find or the page filter.

  3. Switch it on
    Two scopes are available:

    1. State for current user — toggles the feature only for the account you're logged in with. Useful for testing. This rule takes higher priority than the rule applied to the user's group.

    2. Is enabled — the status for all users.
    3. Turn on the relevant switch and save. Front-end status changes require a browser page refresh to take effect; back-end changes do not.
  4.  If the feature isn't in the list
    A feature that hasn't been added to Creatio is treated as disabled. You can add it manually from the same page: create a new record, set Code to EnableDateTimeCalendarListPickerView, add a description, and save. Creatio will populate the Source property with "DbFeatureProvider" automatically. 
Show all comments
auto
filter
Studio_Creatio
8.0

Hi All, question please, so I want to auto filter (not static value) in List, filtered by field with data type Text. How to do that in Creatio, need advice please, thank you.

Capture :

I already tried, but I can not found the way. The only way I can found is Apply static filter from Properties, but it is not my expected. Capture :

I already tried with Business Process too, but the value is not my expected also, because it can not read specific filter result, the result is : showing all data.
Capture :

Need advice please, how to auto filter in List by parameter data type Text (not static filter) ?

Thank you.

Like 0

Like

1 comments

Hello!

The no-code designer cannot do this. The "Apply static filter" dialog only accepts constants or current-user macros, and a business process can populate data but cannot filter a list on a page. To filter a list by a Text field of the current record, you need a few lines of code in the page schema. This is the documented Creatio approach for dynamic list filters.

Every list on a Freedom UI page has a data source attribute in viewModelConfig.attributes with a filterAttributes array. You add your own filter attribute there, then set that attribute to a sdk.FilterGroup in a handler. Whenever the attribute changes, the list reloads with the new filter.

Step 1. Add the SDK dependency

At the top of the page schema (UsrPreScreening_FormPage or whatever your form page is named):

define("UsrPreScreening_FormPage", /**SCHEMA_DEPS*/["@creatio-devkit/common"]/**SCHEMA_DEPS*/, function/**SCHEMA_ARGS*/(sdk)/**SCHEMA_ARGS*/ {

Step 2. Register a filter attribute on the list's data source

Find the data source for your Application list in viewModelConfig.attributes. It will look something like GridDetail_xxxx_DS or DataGrid_xxxx_DS. Add a new entry to its filterAttributes:

"GridDetail_Application_DS": {

    "isCollection": true,

    "modelConfig": {

        "path": "GridDetail_Application_DS",

        "filterAttributes": [

            { "name": "GridDetail_Application_PredefinedFilter", "loadOnChange": false },

            { "name": "GridDetail_Application_ActiveFilter", "loadOnChange": true },

            { "name": "UsrCustomerNameFilter", "loadOnChange": true }

        ]

    }

}

Keep the existing entries; only the last line is new. Also declare the attribute itself next to the others:

"UsrCustomerNameFilter": {}

 

Step 3. Build the filter in handlers

Add two handlers to the handlers array. The first sets the filter when the page opens. The second rebuilds it when the user edits Customer Name. Replace the attribute names with the real ones from your page (PDS_UsrCustomerName_xxxx and PDS_UsrApplicationNumber_xxxx), and the column names with the real Application object column codes.

handlers: /**SCHEMA_HANDLERS*/[
    {
        request: "crt.HandleViewModelInitRequest",
        handler: async (request, next) => {
            await next?.handle(request);
            await applyCustomerNameFilter(request.$context);
        }
    },
    {
        request: "crt.HandleViewModelAttributeChangeRequest",
        handler: async (request, next) => {
            if (request.attributeName === "PDS_UsrCustomerName_xxxx" && !request.silent) {
                await applyCustomerNameFilter(request.$context);
            }
            return next?.handle(request);
        }
    }
]/**SCHEMA_HANDLERS*/,

 

Then define the helper above the return { ... } of the schema module:

async function applyCustomerNameFilter(context) {
    const customerName = context.PDS_UsrCustomerName_xxxx;
    const appNumber = context.PDS_UsrApplicationNumber_xxxx;
    const filter = new sdk.FilterGroup();
    filter.logicalOperation = sdk.LogicalOperation.And;
    if (customerName) {
        await filter.addSchemaColumnFilterWithParameter(
            sdk.ComparisonType.Equal, "UsrCustomerName", customerName);
    } else {
        // no name yet: show nothing instead of everything
        await filter.addSchemaColumnFilterWithParameter(
            sdk.ComparisonType.Equal, "Id", "00000000-0000-0000-0000-000000000000");
    }
    if (appNumber) {
        await filter.addSchemaColumnFilterWithParameter(
            sdk.ComparisonType.NotEqual, "UsrApplicationNumber", appNumber);
    }
    // required workaround: the filter must be a plain object to trigger reload
    const plainFilter = Object.assign({}, filter);
    plainFilter.items = filter.items;
    context.UsrCustomerNameFilter = plainFilter;
}

 

The last three lines are not optional. The list only reloads when the attribute receives a new plain object, so copying the FilterGroup this way is the standard trick.

Notes

  • Use sdk.ComparisonType.Contain instead of Equal if you want partial matches on the name.
  • Column codes in addSchemaColumnFilterWithParameter are Application object column codes, not page attribute names.
  • If you also want Date of Birth or ID Card matching, add more conditions the same way.
  • The static filter you configured in the designer can stay or be removed. Your code filter combines with it through the PredefinedFilter attribute.

Alternative without code

If you can change the data model, make Customer a lookup object instead of a Text field and reference it from both the pre-screening record and the Application object. Then the standard list-to-page binding by lookup column filters automatically. That is the only no-code option, but it only works if a shared lookup makes sense for your data.

Show all comments
calculate
summary
Studio_Creatio
8.0

Hi All, question please.

I already googling also searching in Creatio document, but I can not find the answer.

So, how to calculate total summary from all currencies values in Business Process?
There is no summary function there.


Capture :

 

Need advice please, thank you.

Like 0

Like

1 comments

Hello,

First of all you read only 1 record in the "Read Insurance" read data element (it can be seen from the "First item of resulting collection" macro value). The sum of this will always be the value you read. I think something else was intended here.

Next, you can use Enumerable.Sum method, but you need to get an enumerable. Or introduce a method that can be called inside a script task like (and add Terrasoft.Core.DB to the process usings):

protected decimal GetInsurancePremiTotal(Guid insuranceTypeId) { //or any other argument suitable
    var query = (Select)new Select(UserConnection)
        .Column(Func.Sum("UsrPremi")).As("TotalPremi")
        .From("UsrInsurance").As("usrInsurance").WithHints(new NoLockHint())
        .Where("usrInsurance", "UsrTypeId").IsEqual(Column.Parameter(insuranceTypeId));
    decimal totalPremi = 0;
    using (DBExecutor executor = UserConnection.EnsureDBConnection()) {
        totalPremi = query.ExecuteScalar<decimal>(executor);
    }
    return totalPremi;
}

And set the result to process parameter and then use this parameter value to proceed in the business process.

Show all comments
Studio_Creatio
8.0

Hi all, 

contrarily to normal quickfilters, there does not seem to be a nocode option to filter the owner in the timeline component using nocode tools. Any idea how to fitler the owner, for example by system users of a certain role ?



thanks, 

Damien

Like 0

Like

1 comments

Hello,

As far as I understood you need to filter options in the "Owner" filter dropdown list. There is only one option to do that - override the crt.LoadDataRequest as (and also include "@creatio-devkit/common" to the schema dependencies):

{
                request: "crt.LoadDataRequest",
                handler: async (request, next) => {
                    if (request.dataSourceName?.includes("ByOwnerQuickFilterInTimeline_ComboBox_List_DS")) {
                        const filterGroup = new sdk.FilterGroup();
                        await filterGroup.addSchemaColumnFilterWithParameter(
                            sdk.ComparisonType.Equal, "Name", "Supervisor"); //add other filters here
                            const filterParameter = {
                                type: "filter",
                                value: {
                                    filterType: 6,
                                    logicalOperation: 0,
                                    isEnabled: true,
                                    trimDateTimeParameterToDate: false,
                                    items: filterGroup.items
                                }
                            };
                            request.parameters = request.parameters
                                ? [...request.parameters, filterParameter]
                                : [filterParameter];
                    }
                    return await next?.handle(request);
                }
            }

You also need to confirm the data source name. The easiest way to do that is to add console.log("LoadDataRequest:", request.dataSourceName); to the crt.LoadDataRequest override and see which datasource is logged when you first click this "Owner" filter.

Show all comments
custom
Table
archiving
archive
8.4
Studio_Creatio
8.0

Hello,

I have a custom table in Creatio that stores integration logs for all of our integrations. Over time, the volume of records in this table has grown significantly, and we are now starting to notice an impact on the performance of operations involving this table.

I would like to reduce the size of the active table while still retaining older log records for historical reference and troubleshooting purposes.

Does Creatio provide a built-in mechanism for archiving data from custom tables, or how could this archiving be achieved.

Thank you

Like 0

Like

1 comments

They don't as far as I've found with a few cases of wanting archiving unfortunately.

I've always had to do some workaround such as copying the data using automated SQL scripts into an exact copy of the object for archiving purposes (I have done this by creating a new Object and setting its parent to the Object that needs archiving). This has some downsides that any changes to the regular Object won't apply to the archive Object until you re-publish the archive Object, so something to look out for.

Another issue to watch out for is how the archived data references other data, e.g. if you have parent-child relationships and you are archiving by copying exactly using SQL, the archived record would still be pointing at a "live" record rather than an archived one. Again, custom solutions are required for this.

Overall it's quite a pain, and plenty of issues can arise. It would be great if there were a built in way of doing so.

Show all comments
mobile
file
Studio_Creatio
8.0

How to get file creation date in mobile application when downloading it from a gallery or from a file system?


Here is my code:

onFileTypePickerItemTap: function(el, index, target, record) {
    var source = record.get("Source");
    switch (source) {
        case Terrasoft.Configuration.FilesAndLinksDetailFileSources.Gallery:
            Terrasoft.Camera.captureFromGallery({
                success: this.onCameraCapture,
                failure: this.onFail,
                scope: this
            });
            break;
            ...
        }
    }
},

onCameraCapture: function(fileName, url) {

    Terrasoft.Mask.show({message: Terrasoft.LocalizableStrings.UploadingFile});
    if(fileName && url) {
        this.saveFile(fileName, url);
    } else {
        Terrasoft.Mask.hide();
    }
}

Like 0

Like

1 comments

You can't get the file's real creation date through Terrasoft.Camera. The url your onCameraCapture receives points to a copy the app made in its Downloads folder, and neither platform keeps the original timestamps when copying.

Why:

  • Android: the picker returns a content:// link, but that link never reaches JavaScript. The file is copied twice with plain byte copies, and neither copy keeps the original timestamps.
    • CameraLauncher.getFilePathByUri copies it into the cache (CameraLauncher.java:720-725).
    • Terrasoft.File.copy then copies it into Downloads (LocalFilesystem.java:274-275).
  • iOS: the photo's bytes are written into a new file in Downloads, so it also gets a new timestamp.
  • JS file API: Cordova's File and Metadata objects only expose modification time and size; there is no creation-time field. Terrasoft.File has no metadata method either.

So lastModified on url is simply the moment the user picked the file.

What you can get without changing the app: the EXIF "date taken"

In your call path the copies are byte-for-byte, so a photo's EXIF data survives. You can read DateTimeOriginal (when the photo was taken) from the copy in JS. This held on Android when checked against the source; on iOS it only partly holds (limits below). No EXIF library ships with the mobile web layer, so the code includes a small parser.

onCameraCapture: function(fileName, url) {
    if (!fileName || !url) {                 // cancel: success() is called with no args
        Terrasoft.Mask.hide();
        return;
    }
    Terrasoft.Mask.show({message: Terrasoft.LocalizableStrings.UploadingFile});
    this.readExifDate(fileName, url, function(originalDate) {
        this.saveFile(fileName, url, originalDate);   // Date or null
    }, this);
},

// Calls back exactly once with a Date (EXIF, device-local time) or null.
readExifDate: function(fileName, url, callback, scope) {
    var me = this, called = false;
    var done = function(date) {
        if (!called) { called = true; Ext.callback(callback, scope, [date || null]); }
    };
    var onError = function() { done(null); };
    var onFile = function(file) {                // file is a Terrasoft.File wrapper
        try {
            file.getFileSystemEntry().file(function(cordovaFile) {
                var reader = new FileReader();
                reader.onloadend = function(e) {
                    var date = null;
                    try { date = e.target.result ? me.parseJpegExifDate(e.target.result) : null; } catch (ex) {}
                    done(date);
                };
                reader.readAsArrayBuffer(cordovaFile.slice(0, 131072)); // EXIF sits at the start; avoids loading videos
            }, onError);
        } catch (ex) { done(null); }
    };
    var byUrl = function() {
        Terrasoft.File.resolveLocalFileSystemURI({uri: url, success: onFile, failure: onError});
    };
    if (Terrasoft.Platform.isAndroid) {
        // The Android copy is always Downloads/<fileName>; opening by name avoids re-encoding issues with '#'/'?'
        Terrasoft.File.open({name: Terrasoft.util.getDownloadPath(fileName), success: onFile, failure: byUrl});
    } else {
        byUrl();
    }
},

parseJpegExifDate: function(buffer) {
    var view = new DataView(buffer);
    if (view.byteLength < 4 || view.getUint16(0) !== 0xFFD8) { return null; }   // not a JPEG
    var offset = 2;
    while (offset + 4 <= view.byteLength) {
        var marker = view.getUint16(offset);
        if ((marker & 0xFF00) !== 0xFF00 || marker === 0xFFDA) { return null; }
        if (marker === 0xFFE1 && view.getUint32(offset + 4) === 0x45786966) {   // APP1 "Exif"
            return this.readTiffDate(view, offset + 10);
        }
        offset += 2 + view.getUint16(offset + 2);
    }
    return null;
},

readTiffDate: function(view, tiff) {
    var little = view.getUint16(tiff) === 0x4949;
    var u16 = function(o) { return view.getUint16(o, little); };
    var u32 = function(o) { return view.getUint32(o, little); };
    if (u16(tiff + 2) !== 42) { return null; }
    var findTag = function(ifd, tag) {
        for (var i = 0, n = u16(ifd); i < n; i++) {
            if (u16(ifd + 2 + i * 12) === tag) { return ifd + 2 + i * 12; }
        }
        return -1;
    };
    var toDate = function(entry) {
        if (entry === -1 || u32(entry + 4) < 19) { return null; }
        var start = tiff + u32(entry + 8), s = "";
        for (var i = 0; i < 19; i++) { s += String.fromCharCode(view.getUint8(start + i)); }
        var m = /^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/.exec(s);
        if (!m || +m[1] < 1900) { return null; }                     // "0000:00:00 ..." etc.
        return new Date(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], +m[6]);
    };
    var ifd0 = tiff + u32(tiff + 4), date = null;
    var exifPtr = findTag(ifd0, 0x8769);
    if (exifPtr !== -1) {
        var exifIfd = tiff + u32(exifPtr + 8);
        date = toDate(findTag(exifIfd, 0x9003)) || toDate(findTag(exifIfd, 0x9004)); // DateTimeOriginal, Digitized
    }
    return date || toDate(findTag(ifd0, 0x0132));                    // DateTime (last edit), weakest
}

Limits:

  • JPEG only: you get null for HEIC (the default iPhone camera format), most PNG screenshots, videos, PDFs, and images whose EXIF was stripped (for example, received through messengers).
  • Android re-encoding: if you pass isImageSource: true together with a target size, or pick a PNG, Android re-encodes the image and the EXIF is lost. Its EXIF helper copies only DateTime, not DateTimeOriginal.
  • "File system" (multi-select): the stock generator uses captureFiles, and with UseMobileFileMultiSelect on, the Gallery option does too. The success callback then gets [{fileName, path}]. Call readExifDate(f.fileName, f.path, ...) for each item and save once all callbacks have returned.

Saving the date:

  1. Add a Date/Time column, e.g. UsrOriginalFileDate, to [Entity]File. Don't use CreatedOn; that's when the record was created.
  2. Add the column to the detail's query config (and SyncColumns if you use offline mode).
  3. In saveFile, call record.set("UsrOriginalFileDate", date, true) before saving.

If you need a real file date: change the mobile app

This can only be done in engineering/mobile-app, not in configuration:

  • Android: CameraLauncher.getFileName/getFilePathByUri (CameraLauncher.java:690-725) already opens a cursor on the picked file. Read MediaStore.Images.ImageColumns.DATE_TAKEN, then DocumentsContract.Document.COLUMN_LAST_MODIFIED, then DATE_MODIFIED×1000 from it. Pass the value through processResultFromGallery and add it as a callback argument in camera-provider.js.
  • iOS: in CDVCamera.m (resultForImage/resultForVideo, and the PHPicker path), read PHAsset.creationDate.
  • Even then, Android gives you a "taken" or "modified" time, not a true creation time. It also depends on which app handled the picker.

There is also an iOS-only workaround for single photos: read the Photos asset date from the original assets-library:// link before the copy. I don't recommend it:

  • It relies on private Terrasoft members.
  • It relies on ALAssetsLibrary, deprecated since iOS 9.
  • It needs guards against an unchecked nil in CDVAssetLibraryFilesystem.m:231-233 that can crash the app.
Show all comments
google
calendar
calendar integration
Studio_Creatio
8.0

Hello all,

I recently connected my instance of Creatio to my Google Calendar and the synchronization connected successfully but it brought in only a fraction of events on my calendar. The integration seems to have skipped over every recurring meeting. Is there a way to bring those meetings in? That seems like a huge gap otherwise.

Like 0

Like

1 comments

Outlook calendar integrations also needs improvements, hopefully, the new Graph API integration with Creatio 10 will improve existing behavior and allow more options 🙂. 
First thing asked by Sales teams, and first thing not living to expectations today.

Show all comments
attachments
attachment
delete
deletefile
Studio_Creatio
8.0

Hi All, question please.

I want to delete all attached files using Business Process, I already tried using "Delete Data" with Id = Parameter1.

 

All values in data table is deleted, but the attached files till there.

I tried using "Delete Data" because I read from this article after googling it :

 

Need advice please, and thank you.

Like 0

Like

2 comments

Hi Dedi,

 

Based on you picture, it seems that you are deleting records from Offering letter section, not from attachments that are connected to one specific record in your Offering letter section. If Offering record is custom Freedom UI section than object that should be setup in your "Delete attachment Files" is Uploaded file, and you can filter files based on the filter  Record Id = Office letter Id (Parameter 1). Something like this:

I hope this can help. Let me know if you have some questions.

 

BR,

Jelena

Hi Jelena, thank you for feedback, now I already filter by ID, but not in Business Process, but in Design Form Page.

Thank you Jelena, appreciate it.

Show all comments
currency
separator
decimal
report
Studio_Creatio
8.0

Hi All, question please.

I want to make a currency field in my report using separator decimal.

I already follow this instruction :
https://customerfx.com/article/creating-custom-macros-to-format-values-in-word-printables-for-creatio-formerly-bpmonline/

 

 namespace Terrasoft.Configuration
{
   using System;
   using Terrasoft.Common;
   using Terrasoft.Core;
   using Terrasoft.Core.DB;
   using Terrasoft.Core.Entities;
   using Terrasoft.Core.Packages;
   using Terrasoft.Core.Factories;
   using System.Globalization;
     
   [ExpressionConverterAttribute("Money")]
   public class UsrMoneyConverter : IExpressionConverter
   {
       public string Evaluate(object value, string arguments = "") 
       {
           var result = string.Empty;
           if (value != null)
           {
               result = value.ToString();
               double currency;
               if (Double.TryParse(result, out currency))
               {
                   result = number.Format("{0:n}");
               }
           }
           return result;
       }
   }
}

 

Also, I already tried using :

result = currency.ToString("C", new CultureInfo("en-US"));
 

The result always have this error and the report still not have separator currency decimal :

Need advice please.

Thank you.

Like 0

Like

1 comments

The errors are indicating the code is incorrect in a different schema named "LOSDATAENTERY", not in the code for the macro. Look at the schema named LOSDATAENTRY since it appears to have incorrect code and preventing the compilation from completing. 

Ryan

Show all comments