Estimator Reports Module
The CrestApps.OrchardCore.Estimator.Reports module adds the estimating reports to the CrestApps Reports area, and the Estimator data source to the Report Builder and to Orchard Core data pipelines.
| Feature ID | Name | Category | Dependencies |
|---|---|---|---|
CrestApps.OrchardCore.Estimator.Reports | Estimator Reports | Estimator | CrestApps.OrchardCore.Estimator.Estimates, CrestApps.OrchardCore.Reports |
The Estimator setup recipe enables the feature with CrestApps.OrchardCore.Reports.OpenXml (Excel export) and CrestApps.OrchardCore.Reports.Builder. The design is recorded in docs/engineering/adr/0014-estimating-reports.md in the repository.
Permission
EstimatorReportPermissions.ViewReports (ViewEstimatorReports, View the estimating reports) lets a user run and export the reports. The Administrator role gets it. A report reads only the projects the user may see (EstimatorAccess.CanViewAsync), so an estimator who manages only their own projects sees only those.
Coded reports
Each report is an IReport in the Estimating category, registered with AddReport<TReport>():
| Name | Class | Filter | Content |
|---|---|---|---|
estimator-bid-recap | BidRecapReport | Estimate, revision | Summary metrics, the recap rows (prime cost as a subtotal, the selling price as the grand total), the cost by type, the alternates, and the material and labor of each breakdown node |
estimator-labor | LaborReport | Estimate, revision | Hours and cost by labor category (from the components of each line), a horizontal bar chart, and hours and cost by breakdown node |
estimator-material-list | MaterialListReport | Estimate, revision | Purchase quantities and material by item code, with the items of assemblies; quoted items apart; manual material lines; items not in the catalog |
estimator-quote-summary | QuoteSummaryReport | Estimate, revision | Each package with its quote count, the leveled low quote, what is carried and from where, and the scope gaps without a plug |
estimator-bid-results | BidResultsReport | The date range | The bids due in the period: counts by status, hit rate, value won, a doughnut chart and each bid with its due time in the site's time zone |
The four estimate reports read the base bid: the lines with no AlternateId. A breakdown node includes the nodes under it (EstimateReportWriter.RollUp).
| Service | Lifetime | What it does |
|---|---|---|
EstimateReportDataLoader | Scoped | LoadAsync(user, estimateId, revisionNumber): the project, estimate, revision, lines, recap (EstimateRecap.Calculate), quote packages and quotes, or null when the user may not see the project; ListEstimatesAsync(user) for the filter; ListBidsAsync(user, fromUtc, toUtc) through IBidStore.ListDueBetweenAsync |
EstimateReportWriter | Scoped | Writes each ReportDocument. Amounts are texts in the current culture; totals are Subtotal and GrandTotal rows; warnings use the color #B45309 so they reach the Excel export |
The labor report and the material list expand every line with EstimateLineCalculator.ExplainAllAsync, which loads the catalog once for all lines. Each EstimateLineComponent has its LaborCategory (of its item, of the assembly for the labor of an assembly, or of a manual line) and IsAssemblyLabor for the labor unit of an assembly, which the material list leaves out.
The filter
EstimateReportFilterDisplayDriver adds Estimate and Revision to the filter of the four estimate reports (EstimateReportFilters.EstimateReports). It writes the EstimateId and RevisionNumber keys of the ReportFilter. The Reports page binds the filter on every request under the ReportFilter prefix, so a link can choose the estimate:
/Admin/Reports/view/estimator-bid-recap?ReportFilter.EstimateId={estimateId}&ReportFilter.RevisionNumber=2
The Reports button of the workspace (ReportsEstimateDisplayDriver, EstimateDisplayTypes.WorkspaceActions) opens the bid recap this way, for users with the permission.
The Report Builder data source
With OrchardCore.DataPipelines.Core enabled, DataSourcesStartup registers EstimatorDataSource (Estimator), a RecordDataSource with four data sets:
| Data set | Record | Main date | Key fields | References |
|---|---|---|---|---|
Projects | Project | None | ItemId | OwnerUserId to Users.Users.UserId |
Bids | Bid | DueUtc | ItemId, ProjectId | ProjectId to Estimator.Projects.ItemId |
Estimates | EstimateReportRow (an estimate and its current revision) | None | ItemId, ProjectId | ProjectId, BidId to Estimator.Bids.ItemId |
EstimateLines | EstimateLineReportRow (a line of the current revision, with its breakdown path and alternate code) | None | EstimateId, ProjectId | EstimateId to Estimator.Estimates.ItemId, ProjectId |
- Every data set is read only by a principal for which
EstimatorAccess.CanViewAllAsyncis true. The data sets cannot filter rows per project, so users with only Manage own projects and bids get none. - Projects and bids are read with
ReportIndexPages(newest first, keyset paging, detached from the session). Bids apply the date range onDueUtcand the join keys in the database. - Estimates and lines are read by
EstimateReportPages, 100 estimates at a time by index row, detached from the session. Lines load per revision throughIEstimateLineStore, so the Report Builder row limit (MaxRowsPerDataSet, 50,000 by default) bounds a read.
Tests
tests/CrestApps.OrchardCore.Estimator.Tests/Reports holds the tests of the writer (every report document), and on real SQLite the loader with project access, the reports with their filters, and the four data sets with keys, dates and access.