Fermer

septembre 28, 2022

Comment JADER a utilisé Telerik Report Server à Buku pour créer jCount


Telerik Report Server a été un outil important pour JADER Ltd. dans son projet Buku, aidant à la création du produit de comptabilité j Count®. Laisse moi te montrer les alentours!

j Count® est le produit que nous écrivons et fait partie du Livres projet ou plateforme. Buku, pour les non-initiés, signifie « livre » en malais. Essentiellement, le produit a commencé comme une offre de comptabilité. Il est destiné au marché britannique (initialement) et aux PME. Le produit sera suffisamment robuste et suffisamment fonctionnel pour les grandes organisations au fil du temps. Nous avons d’autres idées car la structure du programme est adaptée à de nombreuses applications de type tableau de bord.

En règle générale, il peut s’exécuter dans un iFrame et en dehors d’un iFrame, de sorte qu’il peut s’installer dans n’importe quel site Web et être marqué en conséquence. Notre souhait est que le programme « appartienne » au client, tout en étant une offre SaaS. (Quand, dans le monde réel, une location est-elle la vôtre ?) Nous voulons également suivre les demandes d’amélioration, c’est donc une philosophie de notre modèle commercial, et tout éditeur de logiciels sait que c’est une demande difficile.

Si vous allez à https://www.buku.uk, vous remarquerez un site Web alternatif dans le menu. Il s’agit d’un simple site Web HTML, CSS et jQuery/JavaScript. S’il existe une chose telle qu’un simple site Web. Le programme fonctionne également sur le site WordPress Buku. Cela illustre la beauté d’un iFrame. N’importe quelle saveur de site peut l’héberger.

Telerik Report Server (TRS) est une offre de Telerik by Progress. Le programme peut rendre des rapports dans une visionneuse, dans les coulisses et de manière programmée. En règle générale, il se connecte à une base de données SQL Server via une chaîne de connexion, mais peut utiliser pratiquement n’importe quelle base de données sur laquelle un fournisseur ADO.NET ou un pilote ODBC est installé, c’est-à-dire que la solution est indépendante de la base de données. La connexion est exposée au rapport pour « voir » les champs de la source de données.

TRS aide à fournir une sortie structurée à partir de l’entrée du logiciel j Count. Ce que nous envisageons, c’est un mécanisme par lequel nous pouvons programmer des rapports, les envoyer par courrier électronique, les imprimer ou les enregistrer. Les données de rapport peuvent être stockées pour un nouveau rendu, et les données stockées peuvent être actualisées de sorte que le rapport puisse inclure des transactions ultérieures. Il s’agit d’une technique d’efficacité, car il n’est pas nécessaire de recalculer toutes les données précédentes.

Nous avons une zone de rapport dans notre programme j Count® qui agit comme une zone de rapport « codée en dur ». Finalement, nous allons proposer une fonction de conception de rapports utilisateur, mais celle-ci n’est pas encore en production.

Chaque rapport dans la zone de rapport a des paramètres pertinents pour le rapport pour permettre le filtrage et la sélection des données sous-jacentes. Nous n’avons pas utilisé la fonction de saisie des paramètres TRS. La éditeur de paramètres personnalisés vaut la peine d’être vérifié pour votre cas d’utilisation, mais nous avons décidé d’utiliser notre propre approche cette fois. Nous choisissons beaucoup notre propre approche car le produit est suffisamment flexible pour nous donner de la place pour cela. Spécifiquement en raison du souhait d’avoir un contrôle plus granulaire sur le fonctionnement des paramètres et l’aspect et la convivialité, nous souhaitions conserver le skin actuel.

Note 1: S’il y avait un moyen de «séparer» les paramètres de rapport de la visionneuse de rapport et de les enchaîner afin qu’il puisse y avoir une activité du contrôleur dans l’obtention des données dans la zone de contrôle des paramètres.

Note 2: Cela peut toujours ne pas fonctionner car certains des paramètres agissent avant le rapport et d’autres agissent pendant le rapport, il devrait donc y avoir une opération en deux phases pour remplir le rapport, ce qui pourrait ne pas être correct, c’est-à-dire pas ce que nous voulons.

Capture d'écran des rapports

Filtres et paramètres de rapport de transaction

Cette page est une version ASP.NET Core C # Razor de l’interface utilisateur Telerik. Plus précisément, un Tabstrip contenant plusieurs barres de panneau, une barre de panneau par rapport.

Parce que je réutilise les filtres dans différentes barres de panneau, j’ai un moyen « intelligent » de les attacher au cshtml. Le principal problème était de savoir comment les nommer, car cette propriété doit être unique dans la collection de contrôles à l’écran.

Ainsi, voici comment j’affiche une plage de dates dans n’importe quelle barre de panneau pour n’importe quel rapport. Plusieurs choses à noter : je passe le modèle (il existe un moyen plus efficace de le faire, j’en suis sûr, mais j’ai utilisé cette méthode dans la preuve de concept et je ne suis pas retourné pour optimiser) au partiel et il est rendu en place . La partie unique est que dans chaque barre de panneau, chaque rapport se voit attribuer de manière unique un entier qui est ajouté à la partie commune du contrôle nommé, rendant ainsi le nom unique sur le cshtml dans son ensemble.

Vous remarquerez un travail pour moi à l’avenir avec tous les contrôles correctement alignés… C’est sur ma liste.

Extrait de code 1 – Instanciation partielle pour la plage de dates

<tr class="trparameters@(this.Model._nReport.ToString())">
    <td>
        @(await this.Html.PartialAsync("_DateRange.cshtml", new Booking.Site.Models.Shared.ReportData()
    {
        _sReport = this.Model._sReport,
        _sBranding = this.Model._sBranding,
        _gId = this.Model._gId,
        _nReport = this.Model._nReport,
        _bPosted = this.Model._bPosted,
        _sHeading = this.Model._sHeading,
        _bAll = this.Model._bAll,
        _bContras = this.Model._bContras,
        _dEnd = this.Model._dEnd,
        _dStart = this.Model._dStart,
        _dAt = this.Model._dAt,
        _dTo = this.Model._dTo
    }))
    </td>
    <td>
    </td>
</tr>

Le fichier _DateRange.cshtml est le suivant :

Le >nom< d'un contrôle de sélecteur de date est comme ici "EndDatePicker" + this.Model._nReport.ToString()

Notez que nous ajoutons le numéro de rapport à tous les contrôles qui peuvent se trouver dans plusieurs barres de panneau sur la page. Si les contrôles n’ont pas de nom unique, une erreur de console JavaScript se produirait.

Extrait de code 2 – Affichage des dates de début et de fin

@model Booking.Site.Models.Shared.ReportData
@using Microsoft.Extensions.Caching.Memory
@inject Booking.Data.DB.Heron28.Heron28Context _oHeron28Context
@inject IMemoryCache _oIMemoryCache
<script>
    if (typeof (AllDatesCheckBox) == 'undefined') {
        function AllDatesCheckBox(e) {
            if (e.checked) {
                $("#StartDatePicker" + e.sender.element.attr("data-report")).data("kendoDatePicker").value('01/01/2000');
                $("#EndDatePicker" + e.sender.element.attr("data-report")).data("kendoDatePicker").value('31/12/2099');
                $("#StartDatePicker" + e.sender.element.attr("data-report")).data("kendoDatePicker").enable(false);
                $("#EndDatePicker" + e.sender.element.attr("data-report")).data("kendoDatePicker").enable(false);
            }
            else {
                $("#StartDatePicker" + e.sender.element.attr("data-report")).data("kendoDatePicker").enable(true);
                $("#EndDatePicker" + e.sender.element.attr("data-report")).data("kendoDatePicker").enable(true);
            }
        }
    }
</script>
<label class="k-label">
    @Booking.Site.Classes.Helper.Text(this.Context, this._oIMemoryCache, this._oHeron28Context, this.User.Identity.Name, "Click to choose all date data?", "Click to choose all date data?", false)
</label>
&nbsp;
<input id="AllDatesCheckBox@(this.Model._nReport.ToString())" data-report="@(this.Model._nReport.ToString())" />
<span class="daterange@(this.Model._nReport.ToString())">
    <br />
    <label class="k-label">
        Start
    </label>
    <br/>
    @Html.Kendo().DatePicker().Name("StartDatePicker" + this.Model._nReport.ToString()).HtmlAttributes(new { style = "width: 200px;" })
    <br/>
    <label class="k-label">
        End
    </label>
    <br />
    @Html.Kendo().DatePicker().Name("EndDatePicker" + this.Model._nReport.ToString()).HtmlAttributes(new { style = "width: 200px;" })
</span>
<script type="text/javascript">
    $(document).ready(function () {
        $("#AllDatesCheckBox@(this.Model._nReport.ToString())").kendoSwitch({
            change: AllDatesCheckBox
        });
    });
</script>
<style>
</style>

L’extrait de code 2 La ligne 10 ne permet d’initialiser les fonctions JavaScript que si elles n’ont pas été initialisées dans une déclaration précédente. Vous n’êtes pas autorisé à déclarer une fonction JavaScript dans le DOM plus d’une fois.

e.sender.element.attr("data-report")— ce morceau de JavaScript lit les données d’un attribut d’un élément de balise HTML. Ainsi, les données peuvent être intégrées dans le modèle utilisé pour rendre les informations.

Extrait de code 3 – Exécuter la routine pour afficher un rapport

function RunReport(nReport, sId) {
            h28_confirm("@Booking.Site.Classes.Helper.Text(this.Context, this._oIMemoryCache, this._oHeron28Context, this.User.Identity.Name, "Are you sure you want to run this report?", "Are you sure you want to run this report?", false)", function() {
                if (sId == null) {
                    sId = sGuidEmpty;
                }
                else {
                    $("#SelectPreRenderedWindow").data("kendoWindow").close();
                }
                switch (nReport) {
                    case @((int)Booking.Library.Classes.Enums.Reports.TimeBatches):
                        {
                            if ($("#AllCheckBox" + nReport.toString()).is(":checked") || ($("#StartDatePicker" + nReport.toString()).data("kendoDatePicker").value() != null && $("#EndDatePicker" + nReport.toString()).data("kendoDatePicker").value() != null)) {
                                var dStart = $("#StartDatePicker" + nReport.toString()).data("kendoDatePicker").value();
                                var dEnd = $("#EndDatePicker" + nReport.toString()).data("kendoDatePicker").value();
                                var bAll = $("#AllCheckBox" + nReport.toString()).is(":checked");
                                h28_CreateWindow(true, "ReportWindow", "Batch Report", function() {
                                    $("#ReportWindow").data("kendoWindow").refresh({
                                        url: '@Url.Content("~/Shared/_ReportViewer")',
                                        data: { lnReport: nReport, lsData: "[sReport]=[Time/Time Batches 1][sJob]=[Job][dStart]=[" + kendo.format("{0:dd-MMM-yyyy}", dStart) + "][dEnd]=[" + kendo.format("{0:dd-MMM-yyyy}", dEnd) + "][bPosted]=[false][bAll]=[" + ((bAll) ? "true" : "false") + "][gInstanceId]=[" + sId + "][sCulture]=[" + sCompanyCulture + "]*" }
                                    }).center().open();
                                }, null);
                            } else {
                                h28_ShowMessage("Please enter a date range or select all data...", sMessageWindow, nShowMessageLevel > 0, function() { }, null);
                            }
                        }
                        break;

L’extrait ci-dessus montre le RunReport fonction du programme, il prend deux paramètres—nReport et sId. La nReport La variable contient un numéro identifiant de manière unique le type de rapport. La sId est l’instance du rapport ou null, selon que les données doivent être collectées au moment de l’exécution ou si les données à utiliser ont déjà été rassemblées.

La raison de la sId est que la vitesse à laquelle les données sont collectées peut être lente en fonction de la taille de la collecte et des spécifications matérielles. Nous pouvons donc réutiliser l’instance de données si l’ensemble de données n’a pas été modifié ou ajouté. À l’avenir, nous allons ajouter une actualisation à laquelle une instance de données peut être ajoutée pour les transactions qui se sont produites après l’exécution qui a créé les données dans le premier cas.

Points à noter :

  1. Le fichier cshtml _ReportViewer est appelé pour chaque rapport.
  2. Le tokenisé lsData string contient les données de paramètres variables à déconstruire dans le contrôleur.
  3. h28_CreateWindow est une routine « homebrew » qui crée et affiche une fenêtre contextuelle Telerik div.

Extrait de code 4 – Contrôleur pour _ReportViewer

public PartialViewResult _ReportViewer(Booking.Site.Models.Shared.ReportData loReportData, int lnReport, string lsData)
{
    Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction loIDbContextTransaction = this._oHeron28Context.Database.BeginTransaction();
    try
    {
        object loData2 = null;
        if (this._oIMemoryCache.TryGetValue("Buku_sData2" + Booking.Data.Classes.Configuration.SessionId(this.HttpContext.Session.GetString("Buku_sCompanyId"), Booking.Site.Classes.Helper.GetUserName(this.HttpContext), Booking.Site.Classes.Helper.GetIPAddress(this.HttpContext), this.HttpContext.Session.Id), out loData2))
        {
            string lsData2 = (string)loData2;
            Booking.Data.DB.Extensions.IdentityExtend.User loUser = this.SelectedUser(lsData2);
            if (loUser != null)
            {
                Guid lgCompanyId = Guid.Parse(Booking.Library.Classes.Utility.DecodeSerialisedData(lsData2, "gCompanyId"));
                Booking.Data.DB.Heron28.Entity loEntity = this._oHeron28Context.Entities.Where(E => E.EGId == lgCompanyId).FirstOrDefault();
                if (loEntity != null)
                {
                    loReportData._nReport = lnReport;
                    loReportData._sData = lsData;
                    object loImage = null;
                    Booking.Data.DB.Heron28.Entity loImagesEntity = this._oHeron28Context.Entities.AsEnumerable().Where(E => E.ECompanyEGId == lgCompanyId && E.ENType == (short)Booking.Library.Classes.Enums.Entities.Images && !E.Deleted).FirstOrDefault();
                    if (loImagesEntity != null)
                    {
                        if (!loEntity.GetDataSetDataset.Company[0].IsCO_nBrandingId1Null() && loEntity.GetDataSetDataset.Company[0].CO_nBrandingId1 != 0 && loImagesEntity.GetDataSetDataset.Images.FindByIMG_nId(loEntity.GetDataSetDataset.Company[0].CO_nBrandingId1) != null)
                        {
                            loImage = loImagesEntity.GetDataSetDataset.Images.FindByIMG_nId(loEntity.GetDataSetDataset.Company[0].CO_nBrandingId1).IMG_oImage;
                        }
                        else
                        {
                            loImage = Booking.Library.Classes.Utility.StreamToBytes(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Booking.Site.Resources.Blank.jpg"));
                        }
                    }
                    else
                    {
                        loImage = Booking.Library.Classes.Utility.StreamToBytes(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Booking.Site.Resources.Blank.jpg"));
                    }
                    if (loImage != null) 
                    {
                        switch ((Booking.Library.Classes.Enums.Reports)lnReport)
                        {
                            case Booking.Library.Classes.Enums.Reports.AdHocSLInvoice1:
                            case Booking.Library.Classes.Enums.Reports.AdHocSLInvoice2:
                            case Booking.Library.Classes.Enums.Reports.AdHocSLCreditNote1:
                            case Booking.Library.Classes.Enums.Reports.AdHocSLCreditNote2:
                            case Booking.Library.Classes.Enums.Reports.AdHocPLInvoice1:
                            case Booking.Library.Classes.Enums.Reports.AdHocPLInvoice2:
                            case Booking.Library.Classes.Enums.Reports.AdHocPLCreditNote1:
                            case Booking.Library.Classes.Enums.Reports.AdHocPLCreditNote2:
                                {
                                    loReportData._sReport = Booking.Library.Classes.Utility.StringValueOf((Booking.Library.Classes.Enums.Reports)lnReport);
                                    loReportData._gId =
                                        Guid.Parse(Booking.Library.Classes.Utility.DecodeSerialisedData(lsData, "gTransactionId"));
                                }
                                break;

Cet extrait de code montre partiellement la méthode du contrôleur _ReportViewer. DecodeSerialisedData montre comment les jetons sont extraits du lsData paramètre.

Qu’est-ce qui est mis dans le ReportData La classe est transmise au moteur de visualisation de rapports _ReportViewer.cshtml. Et le rapport est rendu.

L’idée, ou le problème, résolu ici est le « chaînage » de bout en bout de plusieurs types de rapports de manière cohérente, ce qui signifie que de nouveaux rapports sont « déposés » et que la chaîne est complétée pour qu’ils commencent à fonctionner. Je suppose que plus de travail pourrait être appliqué à la « chaîne », ce qui la rendrait plus automatisée dans son fonctionnement.

Extrait de code 5 – Le visualiseur de rapport lui-même
(Représentation partielle, car l’ensemble est long.)

@model Booking.Site.Models.Shared.ReportData
@using Microsoft.AspNetCore.Identity;
@using Microsoft.Extensions.Caching.Memory
@inject Booking.Data.DB.Heron28.Heron28Context _oHeron28Context
@inject IMemoryCache _oIMemoryCache
@{
}
<script>
    function StoreReport(e)
    {
        h28_confirm("@Booking.Site.Classes.Helper.Text(this.Context, this._oIMemoryCache, this._oHeron28Context, this.User.Identity.Name, "Store Report for User?", "Store Report for User?", false)", function () {
            var oModel =
            {
                _gId: '@this.Model._gInstanceId.ToString()',
                _nReport: @this.Model._nReport.ToString(),
                _sData: '@this.Model._sData'
            };
            $.ajax({
                url: '@Url.Content("~/Shared/_StoreReport")',
                type: 'POST',
                contentType: 'application/json; charset=utf-8;',
                data: kendo.stringify(oModel),
                success: function (oData) {
                    if (oData.bResult) {
                        h28_ShowMessage(oData.sMessage, sMessageWindow, nShowMessageLevel > 1, function () {
                            if ($("#StoredReportsGrid").data("kendoGrid") != null)
                            {
                                $("#StoredReportsGrid").data("kendoGrid").dataSource.read();
                            }
                        }, @Booking.Library.Classes.Constants._nMaxWindowTimeout);
                    }
                    else {
                        h28_ShowMessage(oData.sMessage, sMessageWindow, nShowMessageLevel > 0, function () {
                        }, null);
                    }
                }
            });
        });
    }
     
</script>
@(Html.Kendo().ToolBar()
.Name("AccountSaveAndActionsToolbar")
.Events(E => E.Click("h28_ToolbarClick"))
.Items(items =>
{
    if (string.Format("[{0}][{1}][{2}][{3}]", (int)Booking.Library.Classes.Enums.Reports.AdHocSLInvoice1, (int)Booking.Library.Classes.Enums.Reports.AdHocSLInvoice2, (int)Booking.Library.Classes.Enums.Reports.AdHocSLCreditNote1, (int)Booking.Library.Classes.Enums.Reports.AdHocSLCreditNote2).Contains("[" + this.Model._nReport.ToString() +"]"))
    {
        items.Add().Type(CommandType.Button).Text(Booking.Site.Classes.Helper.Text(this.Context, this._oIMemoryCache, this._oHeron28Context, this.User.Identity.Name, "Issue A New PDF Invoice", "Issue A New PDF Invoice", false)).Icon("plus").HtmlAttributes(new { onclick = "IssueInvoice('" + this.Model._gId.ToString() + "', " + this.Model._nReport.ToString() + ", '" + Booking.Site.Classes.Helper.Text(this.Context, this._oIMemoryCache, this._oHeron28Context, this.User.Identity.Name, "Would you like to issue the Invoice now", "Would you like to issue the Invoice now?", false) + "')" });
    }
    items.Add().Type(CommandType.Button).Text(Booking.Site.Classes.Helper.Text(this.Context, this._oIMemoryCache, this._oHeron28Context, this.User.Identity.Name, "Store Report", "Store Report", false)).Icon("save").Click("StoreReport");
}))
<div id="rvMain" class="k-widget">
    @Booking.Site.Classes.Helper.Text(this.Context, this._oIMemoryCache, this._oHeron28Context, this.User.Identity.Name, "Data is loading, please wait...", "Data is loading, please wait...", false)
</div>
<script type="text/javascript">
    $(document).ready(function () {
        $("#rvMain").telerik_ReportViewer({
            reportServer: {
                url: "@Booking.Data.Classes.Helper.GetCurrentReportServer()",
                username: null,
                password: null
            },
            reportSource: {
                report: '@this.Model._sReport',
                parameters: {
                    @switch ((Booking.Library.Classes.Enums.Reports)this.Model._nReport)
                    {
                        case Booking.Library.Classes.Enums.Reports.AdHocSLInvoice1:
                        case Booking.Library.Classes.Enums.Reports.AdHocSLInvoice2:
                        case Booking.Library.Classes.Enums.Reports.AdHocSLCreditNote1:
                        case Booking.Library.Classes.Enums.Reports.AdHocSLCreditNote2:
                        case Booking.Library.Classes.Enums.Reports.AdHocPLInvoice1:
                        case Booking.Library.Classes.Enums.Reports.AdHocPLInvoice2:
                        case Booking.Library.Classes.Enums.Reports.AdHocPLCreditNote1:
                        case Booking.Library.Classes.Enums.Reports.AdHocPLCreditNote2:
                        {
                            <text>
                                nServerInstance: @Booking.Data.Classes.Configuration._nServerInstance,
                                gTransactionId: '@this.Model._gId.ToString()',
                                gCompanyId: gCompanyId,
                                sCulture: sCompanyCulture
                            </text>
                        }
                            break;

Cela affiche un rapport dans la visionneuse de rapports Telerik dans une bande d’onglets.

On peut voir les paramètres qui sont passés au TRS pour donner quelque chose à faire au rapport. Les autres types de rapport doivent avoir des paramètres différents.

Alors étapes :

  1. La barre de panneau une par rapport contenant les paramètres qui sont envoyés au rapport via le contrôleur ASP.NET.
  2. Créer une méthode de rapport d’exécution qui active le _ReportViewer qui à son tour rassemble les données.
  3. Créez une méthode de visionneuse de rapports avec une méthode de paramètre générique afin que le même cshtml puisse être utilisé pour tous les rapports.

En conclusion, ce n’est pas un exemple complet… Cependant, j’en ai assez fourni pour que l’idée soit reprise et propagée. N’hésitez pas à me contacter pour toute question afin que je puisse vous aider à comprendre.

Il y a plus de code dans GitHub afin que vous puissiez voir la situation dans son ensemble.




Source link

septembre 28, 2022