<link field="reports" reltype="has_many" key="folder" map="" class="rr"/>
</links>
</class>
- <class id="rt" controller="open-ils.reporter-store" oils_obj:fieldmapper="reporter::template" oils_persist:tablename="reporter.template" reporter:label="Template">
+ <class id="rt" controller="open-ils.reporter-store open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="reporter::template" oils_persist:tablename="reporter.template" reporter:label="Template">
<fields oils_persist:primary="id" oils_persist:sequence="reporter.template_id_seq">
<field name="id" reporter:datatype="id" />
<field name="owner" reporter:datatype="link"/>
<link field="folder" reltype="has_a" key="id" map="" class="rtf"/>
<link field="reports" reltype="has_many" key="template" map="" class="rr"/>
</links>
+ <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+ <actions>
+ <create permission="RUN_REPORTS" owning_user="owner" global_required="true"/>
+ <retrieve permission="RUN_REPORTS" owning_user="owner" global_required="true"/>
+ <update permission="RUN_REPORTS" owning_user="owner" global_required="true"/>
+ <delete permission="RUN_REPORTS" owning_user="owner" global_required="true"/>
+ </actions>
+ </permacrud>
</class>
<class id="rr" controller="open-ils.reporter-store" oils_obj:fieldmapper="reporter::report" oils_persist:tablename="reporter.report" reporter:label="Report">
<fields oils_persist:primary="id" oils_persist:sequence="reporter.report_id_seq">
-- -- If we expect to use pcrud to query against specific bibs, we probably want to do this. However, if we're using this to populate a report, we
-- -- may not.
-- SELECT
- -- bre.id AS "bib_id",
- -- COALESCE( z.copy_count, 0 ) AS "copy_count",
- -- COALESCE( z.hold_count, 0 ) AS "hold_count",
- -- COALESCE( z.copy_hold_ratio, 0 ) AS "hold_copy_ratio"
+ -- bre.id AS bib_id,
+ -- COALESCE( z.copy_count, 0 ) AS copy_count,
+ -- COALESCE( z.hold_count, 0 ) AS hold_count,
+ -- COALESCE( z.copy_hold_ratio, 0 ) AS hold_copy_ratio
-- FROM (
SELECT
- y.bre AS "id",
- COALESCE( x.copy_count, 0 ) AS "copy_count",
- y.hold_count AS "hold_count",
- (y.hold_count::REAL / (CASE WHEN x.copy_count = 0 OR x.copy_count IS NULL THEN 0.1 ELSE x.copy_count::REAL END)) AS "hold_copy_ratio"
+ y.bre AS id,
+ COALESCE( x.copy_count, 0 ) AS copy_count,
+ y.hold_count AS hold_count,
+ (y.hold_count::REAL / (CASE WHEN x.copy_count = 0 OR x.copy_count IS NULL THEN 0.1 ELSE x.copy_count::REAL END)) AS hold_copy_ratio
FROM (
SELECT
- (SELECT bib_record FROM reporter.hold_request_record r WHERE r.id = h.id LIMIT 1) AS "bre",
- COUNT(*) AS "hold_count"
+ (SELECT bib_record FROM reporter.hold_request_record r WHERE r.id = h.id LIMIT 1) AS bre,
+ COUNT(*) AS hold_count
FROM action.hold_request h
WHERE
cancel_time IS NULL
(SELECT id
FROM biblio.record_entry
WHERE id = (SELECT record FROM asset.call_number WHERE id = call_number and deleted is false)
- ) AS "bre",
- COUNT(*) AS "copy_count"
+ ) AS bre,
+ COUNT(*) AS copy_count
FROM asset.copy
JOIN asset.copy_location loc ON (copy.location = loc.id AND loc.holdable)
WHERE copy.holdable
</class>
<class id="hasholdscount" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="action::has_holds_count" reporter:label="Copy Has Holds Count" oils_persist:readonly="true">
<oils_persist:source_definition>
- SELECT ahcm.target_copy AS "id",count(*) AS "count"
+ SELECT ahcm.target_copy AS id,count(*) AS count
FROM
action.hold_request ahr,
action.hold_copy_map ahcm
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR;
}
+ $xml =~ s/<!--.*?-->//sg; # filter out XML comments ...
+ $xml =~ s/(?:^|\s+)--.*$//mg; # and SQL comments ...
+ $xml =~ s/^\s+/ /mg; # and extra leading spaces ...
+ $xml =~ s/\R*//g; # and newlines
+
my $output;
try {
my $idl_doc = XML::LibXML->load_xml(string => $xml);
--- /dev/null
+[%
+ WRAPPER "staff/base.tt2";
+ ctx.page_title = l("Reporter");
+ ctx.page_app = "egReporter";
+%]
+
+[% BLOCK APP_JS %]
+<script src="[% ctx.media_prefix %]/js/dojo/opensrf/md5.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/grid.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/eframe.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/ui.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/services/user.js"></script>
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/reporter/services/template.js"></script>
+[% INCLUDE 'staff/reporter/share/report_strings.tt2' %]
+<script src="[% ctx.media_prefix %]/js/ui/default/staff/reporter/template/app.js"></script>
+<link rel="stylesheet" href="[% ctx.base_path %]/staff/css/reporter.css" />
+[% END %]
+
+<div ng-view></div>
+
+[% END %]
--- /dev/null
+
+<script>
+angular.module('egCoreMod').run(['egStrings', function(s) {
+
+s.RPT_BUILDER_CONFIRM_SAVE = '[% l( "Save Template?") %]';
+
+s.SELECT_TFORM = '[% l( "Select Transform") %]';
+s.SELECT_OP = '[% l( "Select Operator") %]';
+
+s.LINK_NULLABLE_DEFAULT = '[% l( "Default") %]';
+s.LINK_NULLABLE_RIGHT = '[% l( "Parent") %]';
+s.LINK_NULLABLE_LEFT = '[% l( "Child") %]';
+s.LINK_NULLABLE_NONE = '[% l( "None") %]';
+
+s.FILTERS_LABEL_EQUALS = '[% l("Equals") %]';
+s.FILTERS_LABEL_LIKE = '[% l( "Contains Matching substring") %]';
+s.FILTERS_LABEL_ILIKE = '[% l( "Contains Matching substring (ignore case)") %]';
+s.FILTERS_LABEL_GREATER_THAN = '[% l( "Greater than") %]';
+s.FILTERS_LABEL_GT_TIME = '[% l( "After (Date/Time)") %]';
+s.FILTERS_LABEL_GT_EQUAL = '[% l( "Greater than or equal to") %]';
+s.FILTERS_LABEL_GTE_TIME = '[% l( "On or After (Date/Time)") %]';
+s.FILTERS_LABEL_LESS_THAN = '[% l( "Less than") %]';
+s.FILTERS_LABEL_LT_TIME = '[% l( "Before (Date/Time)") %]';
+s.FILTERS_LABEL_LT_EQUAL = '[% l( "Less than or equal to") %]';
+s.FILTERS_LABEL_LSE_TIME = '[% l( "On or Before (Date/Time)") %]';
+s.FILTERS_LABEL_IN = '[% l( "In list") %]';
+s.FILTERS_LABEL_NOT_IN = '[% l( "Not in list") %]';
+s.FILTERS_LABEL_BETWEEN = '[% l( "Between") %]';
+s.FILTERS_LABEL_NOT_BETWEEN = '[% l( "Not between") %]';
+s.FILTERS_LABEL_NULL = '[% l( "Is NULL") %]';
+s.FILTERS_LABEL_NOT_NULL = '[% l( "Is not NULL") %]';
+s.FILTERS_LABEL_NULL_BLANK = '[% l( "Is NULL or Blank") %]';
+s.FILTERS_LABEL_NOT_NULL_BLANK = '[% l( "Is not NULL or Blank") %]';
+s.FILTERS_LABEL_EQ_ANY = '[% l( "Equals Any") %]';
+s.FILTERS_LABEL_NE_ANY = '[% l( "Does Not Equal Any") %]';
+
+s.FOLDERS_TEMPLATES = '[% l( "Templates") %]';
+s.FOLDERS_TEMPLATE = '[% l( "Template") %]';
+s.FOLDERS_REPORTS = '[% l( "Reports") %]';
+s.FOLDERS_REPORT = '[% l( "Report") %]';
+s.FOLDERS_OUTPUT = '[% l( "Output") %]';
+
+s.FOLDER_WINDOW_SELECT_ITEM = '[% l( "Please select an item from the list") %]';
+s.FOLDER_WINDOW_CHANGE_FOLDERS = '[% l( "Change Folders") %]';
+s.FOLDER_WINDOW_COLNAME_SELECT = '[% l( "Select") %]';
+s.FOLDER_WINDOW_COLNAME_ALL = '[% l( "All") %]';
+s.FOLDER_WINDOW_COLNAME_NONE = '[% l( "None") %]';
+
+s.REPORT_EDITOR_REPORT_FOLDERS = '[% l( "Report Folders") %]';
+s.REPORT_EDITOR_OUTPUT_FOLDERS = '[% l( "Output Folders") %]';
+s.REPORT_EDITOR_PROVIDE_FOLDER_ALERT = '[% l( "Please provide a report folder") %]';
+s.REPORT_EDITOR_ENTER_NAME_ALERT = '[% l( "Please enter a report name") %]';
+s.REPORT_EDITOR_ENTER_NEW_NAME_ALERT = '[% l( "Please change the report name") %]';
+s.REPORT_EDITOR_INVALID_DATE_ALERT = '[% l( "invalid start date - YYYY-MM-DD") %]';
+s.REPORT_EDITOR_PROVIDE_OUTPUT_ALERT = '[% l( "Please provide an output folder") %]';
+
+s.TFORMS_LABEL_RAW_DATA = '[% l( "Raw Data") %]';
+s.TFORMS_LABEL_FIRST = '[% l( "First Value") %]';
+s.TFORMS_LABEL_LAST = '[% l( "Last Value") %]';
+s.TFORMS_LABEL_COUNT = '[% l( "Count") %]';
+s.TFORMS_LABEL_COUNT_DISTINCT = '[% l( "Count Distinct") %]';
+s.TFORMS_LABEL_MIN = '[% l( "Min") %]';
+s.TFORMS_LABEL_MAX = '[% l( "Max") %]';
+s.TFORMS_LABEL_LOWER = '[% l( "Lower case") %]';
+s.TFORMS_LABEL_UPPER = '[% l( "Upper case") %]';
+s.TFORMS_LABEL_FIRST5 = '[% l( "First 5 characters (for US ZIP code)") %]';
+s.TFORMS_LABEL_FIRST_WORD = '[% l( "First contiguous non-space string") %]';
+s.TFORMS_LABEL_DOW = '[% l( "Day of Week") %]';
+s.TFORMS_LABEL_DOM = '[% l( "Day of Month") %]';
+s.TFORMS_LABEL_DOY = '[% l( "Day of Year") %]';
+s.TFORMS_LABEL_WOY = '[% l( "Week of Year") %]';
+s.TFORMS_LABEL_MOY = '[% l( "Month of Year") %]';
+s.TFORMS_LABEL_QOY = '[% l( "Quarter of Year") %]';
+s.TFORMS_LABEL_HOD = '[% l( "Hour of day") %]';
+s.TFORMS_LABEL_DATE = '[% l( "Date") %]';
+s.TFORMS_LABEL_MONTH_TRUNC = '[% l( "Year + Month") %]';
+s.TFORMS_LABEL_YEAR_TRUNC = '[% l( "Year") %]';
+s.TFORMS_LABEL_HOUR_TRUNC = '[% l( "Hour") %]';
+s.TFORMS_LABEL_DAY_NAME = '[% l( "Day Name") %]';
+s.TFORMS_LABEL_MONTH_NAME = '[% l( "Month Name") %]';
+s.TFORMS_LABEL_AGE = '[% l( "Age") %]';
+s.TFORMS_LABEL_MONTHS_AGO = '[% l( "Months ago") %]';
+s.TFORMS_LABEL_QUARTERS_AGO = '[% l( "Quarters ago") %]';
+s.TFORMS_LABEL_SUM = '[% l( "Sum") %]';
+s.TFORMS_LABEL_AVERAGE = '[% l( "Average") %]';
+s.TFORMS_LABEL_ROUND = '[% l( "Round") %]';
+s.TFORMS_LABEL_INT = '[% l( "Drop trailing decimals") %]';
+
+s.WIDGET_DAYS = '[% l( "Day(s)") %]';
+s.WIDGET_MONTHS = '[% l( "Month(s)") %]';
+s.WIDGET_YEARS = '[% l( "Year(s)") %]';
+s.WIDGET_QUARTERS = '[% l( "Quarter(s)") %]';
+s.WIDGET_REAL_DATE = '[% l( "Real Date") %]';
+s.WIDGET_RELATIVE_DATE = '[% l( "Relative Date") %]';
+
+s.OPERATORS_EQUALS = '[% l( "Equals") %]';
+s.OPERATORS_LIKE = '[% l( "Contains Matching substring") %]';
+s.OPERATORS_ILIKE = '[% l( "Contains Matching substring (ignore case)") %]';
+s.OPERATORS_GREATER_THAN = '[% l( "Greater than") %]';
+s.OPERATORS_GT_TIME = '[% l( "After (Date/Time)") %]';
+s.OPERATORS_GT_EQUAL = '[% l( "Greater than or equal to") %]';
+s.OPERATORS_GTE_TIME = '[% l( "On or After (Date/Time)") %]';
+s.OPERATORS_LESS_THAN = '[% l( "Less than") %]';
+s.OPERATORS_LT_TIME = '[% l( "Before (Date/Time)") %]';
+s.OPERATORS_LT_EQUAL = '[% l( "Less than or equal to") %]';
+s.OPERATORS_LTE_TIME = '[% l( "On or Before (Date/Time)") %]';
+s.OPERATORS_IN_LIST = '[% l( "In list") %]';
+s.OPERATORS_NOT_IN_LIST = '[% l( "Not in list") %]';
+s.OPERATORS_BETWEEN = '[% l( "Between") %]';
+s.OPERATORS_NOT_BETWEEN = '[% l( "Not between") %]';
+s.OPERATORS_IS_NULL = '[% l( "Is NULL") %]';
+s.OPERATORS_IS_NOT_NULL = '[% l( "Is not NULL") %]';
+s.OPERATORS_NULL_BLANK = '[% l( "Is NULL or Blank") %]';
+s.OPERATORS_NOT_NULL_BLANK = '[% l( "Is not NULL or Blank") %]';
+s.OPERATORS_EQ_ANY = '[% l( "Equals Any") %]';
+s.OPERATORS_NE_ANY = '[% l( "Does Not Equal Any") %]';
+
+s.SOURCE_BROWSE_AGGREGATE = '[% l( "Aggregate") %]';
+s.SOURCE_BROWSE_NON_AGGREGATE = '[% l( "Non-Aggregate") %]';
+
+s.SOURCE_SETUP_CONFIRM_EXIT = '[% l( "You have started building a template! Selecting a new starting source will destroy the current template and start over. Is this OK?") %]';
+s.SOURCE_SETUP_CORE_SOURCES = '[% l( "Core Sources") %]';
+s.SOURCE_SETUP_ALL_AVAIL_SOURCES = '[% l( "All Available Sources") %]';
+
+s.TEMPLATE_CONF_BARE = '[% l( "Bare") %]';
+s.TEMPLATE_CONF_RAW_DATA = '[% l( "Raw Data") %]';
+s.TEMPLATE_CONF_EQUALS = '[% l( "Equals") %]';
+s.TEMPLATE_CONF_CONFIRM_RESET = '[% l( "You have already added this field. Click OK if you would like to reset this field.") %]';
+s.TEMPLATE_CONF_PROMPT_CHANGE = '[% l( "Change the column header?") %]';
+s.TEMPLATE_FIELD_DOC_PROMPT_CHANGE = '[% l( "Change the field hint to:") %]';
+s.TEMPLATE_CONF_BOOLEAN_VALUE = '[% l( "Boolean Value") %]';
+s.TEMPLATE_CONF_SELECT_CANCEL = '[% l( "Select the value, or cancel:") %]';
+s.TEMPLATE_CONF_TRUE = '[% l( "True") %]';
+s.TEMPLATE_CONF_FALSE = '[% l( "False") %]';
+s.TEMPLATE_CONF_CONFIRM_STATE = '[% l( "Click OK for TRUE and Cancel for FALSE.") %]';
+s.TEMPLATE_CONF_NO_MATCH = '[% l( "Field does not match one of list (comma separated):") %]';
+s.TEMPLATE_CONF_NOT_BETWEEN = '[% l( "Field value is not between (comma separated):") %]';
+s.TEMPLATE_CONF_BETWEEN = '[% l( "Field value is between (comma separated):") %]';
+s.TEMPLATE_CONF_NOT_IN = '[% l( "Field does not match one of list (comma separated):") %]';
+s.TEMPLATE_CONF_IN = '[% l( "Field matches one of list (comma separated):") %]';
+s.TEMPLATE_CONF_DEFAULT = '[% l( "Value:") %]';
+s.TEMPLATE_CONF_CONFIRM_SAVE = '[% l( "Save Template?") %]';
+s.TEMPLATE_CONF_SUCCESS_SAVE = '[% l( "Template was successfully saved.") %]';
+s.TEMPLATE_CONF_FAIL_SAVE = '[% l( "Template save failed.") %]';
+
+s.TRANSFORMS_BARE = '[% l( "Raw Data") %]';
+s.TRANSFORMS_FIRST = '[% l( "First Value") %]';
+s.TRANSFORMS_LAST = '[% l( "Last Value") %]';
+s.TRANSFORMS_COUNT = '[% l( "Count") %]';
+s.TRANSFORMS_COUNT_DISTINCT = '[% l( "Count Distinct") %]';
+s.TRANSFORMS_MIN = '[% l( "Min") %]';
+s.TRANSFORMS_MAX = '[% l( "Max") %]';
+s.TRANSFORMS_SUBSTRING = '[% l( "Substring") %]';
+s.TRANSFORMS_LOWER = '[% l( "Lower case") %]';
+s.TRANSFORMS_UPPER = '[% l( "Upper case") %]';
+s.TRANSFORMS_FIRST5 = '[% l( "First 5 characters (for US ZIP code)") %]';
+s.TRANSFORMS_FIRST_WORD = '[% l( "First contiguous non-space string") %]';
+s.TRANSFORMS_DOW = '[% l( "Day of Week") %]';
+s.TRANSFORMS_DOM = '[% l( "Day of Month") %]';
+s.TRANSFORMS_DOY = '[% l( "Day of Year") %]';
+s.TRANSFORMS_WOY = '[% l( "Week of Year") %]';
+s.TRANSFORMS_MOY = '[% l( "Month of Year") %]';
+s.TRANSFORMS_QOY = '[% l( "Quarter of Year") %]';
+s.TRANSFORMS_HOD = '[% l( "Hour of day") %]';
+s.TRANSFORMS_DATE = '[% l( "Date") %]';
+s.TRANSFORMS_MONTH_TRUNC = '[% l( "Year + Month") %]';
+s.TRANSFORMS_YEAR_TRUNC = '[% l( "Year") %]';
+s.TRANSFORMS_HOUR_TRUNC = '[% l( "Hour") %]';
+s.TRANSFORMS_DAY_NAME = '[% l( "Day Name") %]';
+s.TRANSFORMS_MONTH_NAME = '[% l( "Month Name") %]';
+s.TRANSFORMS_AGE = '[% l( "Age") %]';
+s.TRANSFORMS_MONTHS_AGO = '[% l( "Months ago") %]';
+s.TRANSFORMS_QUARTERS_AGO = '[% l( "Quarters ago") %]';
+s.TRANSFORMS_SUM = '[% l( "Sum") %]';
+s.TRANSFORMS_AVERAGE = '[% l( "Average") %]';
+s.TRANSFORMS_ROUND = '[% l( "Round") %]';
+s.TRANSFORMS_INT = '[% l( "Drop trailing decimals") %]';
+
+}]);
+</script>
--- /dev/null
+<!-- report template builder -->
+
+<div class="row">
+ <div class="col-md-2">
+ [% l('Template Name') %]
+ </div>
+ <div class="col-md-4">
+ <div><input type="text" class="form-control" ng-model="templateName"/></div>
+ </div>
+ <div class="col-md-2">
+ [% l('Documentation URL') %]
+ </div>
+ <div class="col-md-4">
+ <div><input type="text" class="form-control" ng-model="templateDocURL"/></div>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-2">
+ [% l('Template Description') %]
+ </div>
+ <div class="col-md-10">
+ <div><textarea class="form-control" ng-model="templateDescription"/></div>
+ </div>
+</div>
+
+<div class="row">
+ <div class="col-md-2">
+ <button ng-click="saveTemplate()" class="btn btn-default">[% l('Save Template') %]</button>
+ </div>
+</div>
+
+<hr/>
+
+<div class="row panel" style="max-height: 400px; min-height: 400px;">
+ <div class="col-md-5" style="max-height: 400px; min-height: 400px; overflow-y: scroll;">
+
+ <div class="row">
+ <div class="col-xs-3"><strong>[% l('Core Source') %]</strong></div>
+ <div class="col-xs-6">
+ <div class="source-selector nullable">
+ <select class="form-control" ng-model="coreSource" ng-change="changeCoreSource()"
+ ng-options="s.name as s.label group by s.core_label for s in allSources">
+ <option value="">[% l('-- Select Source --') %]</option>
+ </select>
+ </div>
+ </div>
+ <div class="col-xs-3">
+ <label for="enable_nullability_cb">
+ [% l('Nullability') %]
+ </lable>
+ <input type="checkbox" ng-model="enable_nullability"/>
+ </div>
+ </div>
+
+ <br/>
+
+ <treecontrol
+ class="tree-light"
+ tree-model="class_tree"
+ on-node-toggle="treeExpand(node, expanded)"
+ on-selection="selectSource(node, selected, $path)"
+ >
+ <select
+ ng-show="enable_nullability"
+ ng-model="node.jtype"
+ ng-init="join_types = [{type:'inner',label:'[% l('Default') %]'},{type:'left',label:'[% l('Child nullable') %]'},{type:'right',label:'[% l('Parent nullable') %]'}]"
+ ng-options="j.type as j.label for j in join_types"></select>
+ {{ node.label || n.id }}
+ </treecontrol>
+
+ </div>
+ <div class="col-md-7">
+ <div class="row">
+ <div class="col-md-7" style="max-height: 400px; min-height: 400px; overflow-y: scroll;">
+ <div class="row">
+ <div class="col-xs-3"><strong>[% l('Source Path') %]</strong></div>
+ <div class="col-xs-9"><input type="text" class="form-control" ng-model="currentPathLabel"/></div>
+ </div>
+
+ <br/>
+
+ <treecontrol
+ class="tree-light"
+ tree-model="selected_source_fields"
+ selected-nodes="selected_source_field_list"
+ on-selection="selectFields()"
+ options="field_tree_opts"
+ filter-expression="filterFields"
+ >
+ <span ng-switch="" on="node.datatype">
+ <span ng-switch-when="bool" class="glyphicon glyphicon-ok-sign" aria-hidden="true"></span>
+ <span ng-switch-when="float" class="glyphicon glyphicon-sound-5-1" aria-hidden="true"></span>
+ <span ng-switch-when="id" class="glyphicon glyphicon-barcode" aria-hidden="true"></span>
+ <span ng-switch-when="int" class="glyphicon glyphicon-scale" aria-hidden="true"></span>
+ <span ng-switch-when="interval" class="glyphicon glyphicon-resize-horizontal" aria-hidden="true"></span>
+ <span ng-switch-when="link" class="glyphicon glyphicon-link" aria-hidden="true"></span>
+ <span ng-switch-when="money" class="glyphicon glyphicon-usd" aria-hidden="true"></span>
+ <span ng-switch-when="number" class="glyphicon glyphicon-scale" aria-hidden="true"></span>
+ <span ng-switch-when="org_unit" class="glyphicon glyphicon-tree-conifer" aria-hidden="true"></span>
+ <span ng-switch-when="text" class="glyphicon glyphicon-font" aria-hidden="true"></span>
+ <span ng-switch-when="timestamp" class="glyphicon glyphicon-calendar" aria-hidden="true"></span>
+ </span>
+ {{ node.label || node.name }}
+ </treecontrol>
+ </div>
+ <div class="col-md-5" style="max-height: 400px; min-height: 400px; overflow-y: scroll;">
+ <strong>[% l('Transform') %]</strong>
+ <br/>
+ <br/>
+ <br/>
+ <treecontrol
+ class="tree-light"
+ tree-model="available_field_transforms"
+ selected-node="selected_transform"
+ options="field_transforms_tree_opts"
+ >
+ {{ node.label || node.transform }}
+ </treecontrol>
+ </div>
+ </div>
+ </div>
+
+ <hr/>
+
+ <div class="row">
+ <div class="col-md-12">
+
+ <uib-tabset>
+ <uib-tab index="0" heading="[% l('Display Fields') %]">
+ <eg-grid
+ id-field="index"
+ features="-sort,-multisort,-multiselect"
+ items-provider="grid_display_fields_provider"
+ grid-controls="display_grid_controls"
+ >
+ <eg-grid-action
+ handler="changeDisplayLabel"
+ label="[% l('Change Column Label') %]">
+ </eg-grid-action>
+
+ <eg-grid-action
+ handler="changeDisplayFieldDoc"
+ label="[% l('Change Column Documentation') %]">
+ </eg-grid-action>
+
+ <eg-grid-action
+ handler="changeTransform"
+ label="[% l('Change Transform') %]">
+ </eg-grid-action>
+
+ <eg-grid-action
+ handler="moveDisplayFieldUp"
+ label="[% l('Move Field Up') %]">
+ </eg-grid-action>
+
+ <eg-grid-action
+ handler="moveDisplayFieldDown"
+ label="[% l('Move Field Down') %]">
+ </eg-grid-action>
+
+ <eg-grid-action
+ handler="removeDisplayField"
+ label="[% l('Remove Field') %]">
+ </eg-grid-action>
+
+ <eg-grid-menu-item handler="addDisplayFields"
+ label="[% l('Add Fields') %]"></eg-grid-menu-item>
+
+ <eg-grid-field path='path_label' label="[% l('Source Path') %]"></eg-grid-field>
+ <eg-grid-field path='name' label="[% l('Column') %]" hidden></eg-grid-field>
+ <eg-grid-field path='doc_text' label="[% l('Documentation') %]" hidden></eg-grid-field>
+ <eg-grid-field path='label' label="[% l('Column Label') %]"></eg-grid-field>
+ <eg-grid-field path='datatype' label="[% l('Data Type') %]"></eg-grid-field>
+ <eg-grid-field path='transform.label' label="[% l('Field Transform') %]"></eg-grid-field>
+ </eg-grid>
+ </uib-tab>
+
+ <uib-tab index="1" heading="[% l('Filters') %]">
+ <eg-grid
+ id-field="index"
+ features="-sort,-multisort,-multiselect"
+ items-provider="grid_filter_fields_provider"
+ grid-controls="filter_grid_controls"
+ >
+ <eg-grid-action
+ handler="changeFilterFieldDoc"
+ label="[% l('Change Column Documentation') %]">
+ </eg-grid-action>
+
+ <eg-grid-action
+ handler="changeTransform"
+ label="[% l('Change Transform') %]">
+ </eg-grid-action>
+
+ <eg-grid-action
+ handler="changeOperator"
+ label="[% l('Change Operator') %]">
+ </eg-grid-action>
+
+ <eg-grid-action
+ handler="changeFilterValue"
+ label="[% l('Change Filter Value') %]">
+ </eg-grid-action>
+
+ <eg-grid-action
+ handler="removeFilterValue"
+ label="[% l('Remove Filter Value') %]">
+ </eg-grid-action>
+
+ <eg-grid-action
+ handler="removeFilterField"
+ label="[% l('Remove Field') %]">
+ </eg-grid-action>
+
+ <eg-grid-menu-item handler="addFilterFields"
+ label="[% l('Add Fields') %]"></eg-grid-menu-item>
+
+ <eg-grid-field path='path_label' label="[% l('Source Path') %]"></eg-grid-field>
+ <eg-grid-field path='label' label="[% l('Name') %]"></eg-grid-field>
+ <eg-grid-field path='name' label="[% l('Column') %]"></eg-grid-field>
+ <eg-grid-field path='datatype' label="[% l('Data Type') %]"></eg-grid-field>
+ <eg-grid-field path='operator.label' label="[% l('Operator') %]"></eg-grid-field>
+ <eg-grid-field path='transform.label' label="[% l('Field Transform') %]"></eg-grid-field>
+ <eg-grid-field path='value' label="[% l('Filter Value') %]"></eg-grid-field>
+ </eg-grid>
+ </uib-tab>
+ </uib-tabset>
+
+ </div>
+ </div>
+</div>
+
--- /dev/null
+<eg-embed-frame save-space="150" url="rurl"></eg-embed-frame>
<div class="modal-footer">
[% dialog_footer %]
<input type="submit" class="btn btn-primary"
- ng-click="ok()" value="{{ ok_button_label || '[% l('OK/Continue') %]'}}"/>
+ ng-click="ok()" value="{{ ok_button_label || '[% l("OK/Continue") %]'}}"/>
<button class="btn btn-warning"
ng-click="cancel()">{{ cancel_button_label || "[% l('Cancel') %]"}}</button>
</div>
--- /dev/null
+<!--
+ Generic confirmation dialog
+-->
+<div>
+ <div class="modal-header">
+ <button type="button" class="close"
+ ng-click="cancel()" aria-hidden="true">×</button>
+ <h4 class="modal-title alert alert-info">{{message}}</h4>
+ </div>
+ <div class="modal-body">
+ <div class="row">
+ <div class="col-md-12">
+ <eg-basic-combo-box allow-all="true" list="args.list" selected="args.value" focus-me="focus"></eg-basic-combo-box>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ [% dialog_footer %]
+ <input type="submit" class="btn btn-primary"
+ ng-click="ok()" value="[% l('OK/Continue') %]"/>
+ <button class="btn btn-warning" ng-click="cancel()">[% l('Cancel') %]</button>
+ </div>
+</div>
"angular-file-saver": "~1.1.0",
"angular-location-update": "~0.0.2",
"ngtoast": "~2.0.0",
- "angular-tree-control": "~0.2.23",
+ "angular-tree-control": "~0.2.28",
"angular-animate": "~1.5.3",
"angular-hotkeys": "cfp-angular-hotkeys#^1.7.0"
},
--- /dev/null
+/**
+ * Report templates
+ */
+
+angular.module('egReportMod', ['egCoreMod', 'ui.bootstrap'])
+.factory('egReportTemplateSvc',
+
+ ['$uibModal','$q','egCore','egConfirmDialog','egAlertDialog',
+function($uibModal , $q , egCore , egConfirmDialog , egAlertDialog) {
+
+ //dojo.requireLocalization("openils.reports", "reports");
+ //var egCore.strings = dojo.i18n.getLocalization("openils.reports", "reports");
+
+ var OILS_RPT_DTYPE_ARRAY = 'array';
+ var OILS_RPT_DTYPE_STRING = 'text';
+ var OILS_RPT_DTYPE_MONEY = 'money';
+ var OILS_RPT_DTYPE_BOOL = 'bool';
+ var OILS_RPT_DTYPE_INT = 'int';
+ var OILS_RPT_DTYPE_ID = 'id';
+ var OILS_RPT_DTYPE_OU = 'org_unit';
+ var OILS_RPT_DTYPE_FLOAT = 'float';
+ var OILS_RPT_DTYPE_TIMESTAMP = 'timestamp';
+ var OILS_RPT_DTYPE_INTERVAL = 'interval';
+ var OILS_RPT_DTYPE_LINK = 'link';
+ var OILS_RPT_DTYPE_NONE = '';
+ var OILS_RPT_DTYPE_NULL = null;
+ var OILS_RPT_DTYPE_UNDEF;
+
+ var OILS_RPT_DTYPE_ALL = [
+ OILS_RPT_DTYPE_STRING,
+ OILS_RPT_DTYPE_MONEY,
+ OILS_RPT_DTYPE_INT,
+ OILS_RPT_DTYPE_ID,
+ OILS_RPT_DTYPE_FLOAT,
+ OILS_RPT_DTYPE_TIMESTAMP,
+ OILS_RPT_DTYPE_BOOL,
+ OILS_RPT_DTYPE_OU,
+ OILS_RPT_DTYPE_NONE,
+ OILS_RPT_DTYPE_NULL,
+ OILS_RPT_DTYPE_UNDEF,
+ OILS_RPT_DTYPE_INTERVAL,
+ OILS_RPT_DTYPE_LINK
+ ];
+ var OILS_RPT_DTYPE_NOT_ID = [OILS_RPT_DTYPE_STRING,OILS_RPT_DTYPE_MONEY,OILS_RPT_DTYPE_INT,OILS_RPT_DTYPE_FLOAT,OILS_RPT_DTYPE_TIMESTAMP];
+ var OILS_RPT_DTYPE_NOT_BOOL = [OILS_RPT_DTYPE_STRING,OILS_RPT_DTYPE_MONEY,OILS_RPT_DTYPE_INT,OILS_RPT_DTYPE_FLOAT,OILS_RPT_DTYPE_TIMESTAMP,OILS_RPT_DTYPE_ID,OILS_RPT_DTYPE_OU,OILS_RPT_DTYPE_LINK];
+
+ var service = {
+ display_fields : [],
+ filter_fields : [],
+
+ Filters : {
+ '=' : {
+ label : egCore.strings.OPERATORS_EQUALS
+ },
+
+ 'like' : {
+ label : egCore.strings.OPERATORS_LIKE
+ },
+
+ ilike : {
+ label : egCore.strings.OPERATORS_ILIKE
+ },
+
+ '>' : {
+ label : egCore.strings.OPERATORS_GREATER_THAN,
+ labels : { timestamp : egCore.strings.OPERATORS_GT_TIME }
+ },
+
+ '>=' : {
+ label : egCore.strings.OPERATORS_GT_EQUAL,
+ labels : { timestamp : egCore.strings.OPERATORS_GTE_TIME }
+ },
+
+
+ '<' : {
+ label : egCore.strings.OPERATORS_LESS_THAN,
+ labels : { timestamp : egCore.strings.OPERATORS_LT_TIME }
+ },
+
+ '<=' : {
+ label : egCore.strings.OPERATORS_LT_EQUAL,
+ labels : { timestamp : egCore.strings.OPERATORS_LTE_TIME }
+ },
+
+ 'in' : {
+ label : egCore.strings.OPERATORS_IN_LIST
+ },
+
+ 'not in' : {
+ label : egCore.strings.OPERATORS_NOT_IN_LIST
+ },
+
+ 'between' : {
+ label : egCore.strings.OPERATORS_BETWEEN
+ },
+
+ 'not between' : {
+ label : egCore.strings.OPERATORS_NOT_BETWEEN
+ },
+
+ 'is' : {
+ label : egCore.strings.OPERATORS_IS_NULL
+ },
+
+ 'is not' : {
+ label : egCore.strings.OPERATORS_IS_NOT_NULL
+ },
+
+ 'is blank' : {
+ label : egCore.strings.OPERATORS_NULL_BLANK
+ },
+
+ 'is not blank' : {
+ label : egCore.strings.OPERATORS_NOT_NULL_BLANK
+ },
+
+ '= any' : {
+ labels : { 'array' : egCore.strings.OPERATORS_EQ_ANY }
+ },
+
+ '<> any' : {
+ labels : { 'array' : egCore.strings.OPERATORS_NE_ANY }
+ }
+ },
+
+ Transforms : {
+ Bare : {
+ datatype : OILS_RPT_DTYPE_ALL,
+ label : egCore.strings.TRANSFORMS_BARE
+ },
+
+ first : {
+ datatype : OILS_RPT_DTYPE_NOT_ID,
+ label : egCore.strings.TRANSFORMS_FIRST
+ },
+
+ last : {
+ datatype : OILS_RPT_DTYPE_NOT_ID,
+ label : egCore.strings.TRANSFORMS_LAST
+ },
+
+ count : {
+ datatype : OILS_RPT_DTYPE_NOT_BOOL,
+ aggregate : true,
+ label : egCore.strings.TRANSFORMS_COUNT
+ },
+
+ count_distinct : {
+ datatype : OILS_RPT_DTYPE_NOT_BOOL,
+ aggregate : true,
+ label : egCore.strings.TRANSFORMS_COUNT_DISTINCT
+ },
+
+ min : {
+ datatype : OILS_RPT_DTYPE_NOT_ID,
+ aggregate : true,
+ label : egCore.strings.TRANSFORMS_MIN
+ },
+
+ max : {
+ datatype : OILS_RPT_DTYPE_NOT_ID,
+ aggregate : true,
+ label : egCore.strings.TRANSFORMS_MAX
+ },
+
+ /* string transforms ------------------------- */
+
+ substring : {
+ datatype : [ OILS_RPT_DTYPE_STRING ],
+ params : 2,
+ label : egCore.strings.TRANSFORMS_SUBSTRING
+ },
+
+ lower : {
+ datatype : [ OILS_RPT_DTYPE_STRING ],
+ label : egCore.strings.TRANSFORMS_LOWER
+ },
+
+ upper : {
+ datatype : [ OILS_RPT_DTYPE_STRING ],
+ label : egCore.strings.TRANSFORMS_UPPER
+ },
+
+ first5 : {
+ datatype : [ OILS_RPT_DTYPE_STRING ],
+ label : egCore.strings.TRANSFORMS_FIRST5
+ },
+
+ first_word : {
+ datatype : [OILS_RPT_DTYPE_STRING, 'text'],
+ label : egCore.strings.TRANSFORMS_FIRST_WORD
+ },
+
+ /* timestamp transforms ----------------------- */
+ dow : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_DOW,
+ cal_format : '%w',
+ regex : /^[0-6]$/
+ },
+ dom : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_DOM,
+ cal_format : '%e',
+ regex : /^[0-9]{1,2}$/
+ },
+
+ doy : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_DOY,
+ cal_format : '%j',
+ regex : /^[0-9]{1,3}$/
+ },
+
+ woy : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_WOY,
+ cal_format : '%U',
+ regex : /^[0-9]{1,2}$/
+ },
+
+ moy : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_MOY,
+ cal_format : '%m',
+ regex : /^\d{1,2}$/
+ },
+
+ qoy : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_QOY,
+ regex : /^[1234]$/
+ },
+
+ hod : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_HOD,
+ cal_format : '%H',
+ regex : /^\d{1,2}$/
+ },
+
+ date : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_DATE,
+ regex : /^\d{4}-\d{2}-\d{2}$/,
+ hint : 'YYYY-MM-DD',
+ cal_format : '%Y-%m-%d',
+ input_size : 10
+ },
+
+ month_trunc : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_MONTH_TRUNC,
+ regex : /^\d{4}-\d{2}$/,
+ hint : 'YYYY-MM',
+ cal_format : '%Y-%m',
+ input_size : 7
+ },
+
+ year_trunc : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_YEAR_TRUNC,
+ regex : /^\d{4}$/,
+ hint : 'YYYY',
+ cal_format : '%Y',
+ input_size : 4
+ },
+
+ hour_trunc : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_HOUR_TRUNC,
+ regex : /^\d{2}$/,
+ hint : 'HH',
+ cal_format : '%Y-%m-$d %H',
+ input_size : 2
+ },
+
+ day_name : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ cal_format : '%A',
+ label : egCore.strings.TRANSFORMS_DAY_NAME
+ },
+
+ month_name : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ cal_format : '%B',
+ label : egCore.strings.TRANSFORMS_MONTH_NAME
+ },
+ age : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_AGE
+ },
+
+ months_ago : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_MONTHS_AGO
+ },
+
+ quarters_ago : {
+ datatype : [ OILS_RPT_DTYPE_TIMESTAMP ],
+ label : egCore.strings.TRANSFORMS_QUARTERS_AGO
+ },
+
+ /* int / float transforms ----------------------------------- */
+ sum : {
+ datatype : [ OILS_RPT_DTYPE_INT, OILS_RPT_DTYPE_FLOAT, OILS_RPT_DTYPE_MONEY ],
+ label : egCore.strings.TRANSFORMS_SUM,
+ aggregate : true
+ },
+
+ average : {
+ datatype : [ OILS_RPT_DTYPE_INT, OILS_RPT_DTYPE_FLOAT, OILS_RPT_DTYPE_MONEY ],
+ label : egCore.strings.TRANSFORMS_AVERAGE,
+ aggregate : true
+ },
+
+ round : {
+ datatype : [ OILS_RPT_DTYPE_INT, OILS_RPT_DTYPE_FLOAT ],
+ label : egCore.strings.TRANSFORMS_ROUND,
+ },
+
+ 'int' : {
+ datatype : [ OILS_RPT_DTYPE_FLOAT ],
+ label : egCore.strings.TRANSFORMS_INT
+ }
+ }
+ };
+
+ service.addFields = function (type, fields, transform, source_label, source_path, op) {
+ fields.forEach(function(f) {
+ var l = f.label ? f.label : f.name;
+ var new_field = angular.extend(
+ {},
+ f,
+ { index : service[type].length,
+ label : l,
+ path : source_path,
+ path_label : source_label,
+ operator : op,
+ transform : transform,
+ doc_text : ''
+ }
+ );
+
+ var add = true;
+ service[type].forEach(function(e) {
+ if (e.name == new_field.name && e.path == new_field.path) add = false;
+ });
+ if (add) service[type].push(new_field);
+ });
+ }
+
+ service.moveFieldUp = function (type, field) {
+ var new_list = [];
+ while (service[type].length) {
+ var f = service[type].pop();
+ if (field.index == f.index && f.index > 0)
+ new_list.unshift(f,service[type].pop());
+ else
+ new_list.unshift(f);
+ }
+ new_list.forEach(function(f) {
+ service[type].push(angular.extend(f, { index : service[type].length}));
+ });
+ }
+
+ service.moveFieldDown = function (type, field) {
+ var new_list = [];
+ var start_len = service[type].length - 1;
+ while (service[type].length) {
+ var f = service[type].shift();
+ if (field.index == f.index && f.index < start_len)
+ new_list.push(service[type].shift(),f);
+ else
+ new_list.push(f);
+ }
+ new_list.forEach(function(f) {
+ service[type].push(angular.extend(f, { index : service[type].length}));
+ });
+ }
+
+ service.removeField = function (type, field) {
+ var new_list = [];
+ while (service[type].length) {
+ var f = service[type].pop();
+ if (field.index != f.index ) new_list.push(f);
+ }
+ new_list.forEach(function(f) {
+ service[type].push(angular.extend(f, { index : service[type].length}));
+ });
+ }
+
+ service.getTransformByLabel = function (l) {
+ for( var key in service.Transforms ) {
+ var t = service.Transforms[key];
+ if (l == t.label) return key;
+ if (angular.isArray(t.labels) && t.labels.indexOf(l) > -1) return key;
+ }
+ return null;
+ }
+
+ service.getFilterByLabel = function (l) {
+ for( var key in service.Filters ) {
+ var t = service.Filters[key];
+ if (l == t.label) return key;
+ if (angular.isArray(t.labels) && t.labels.indexOf(l) > -1) return key;
+ }
+ return null;
+ }
+
+ service.getTransforms = function (args) {
+ var dtype = args.datatype;
+ var agg = args.aggregate;
+ var nonagg = args.non_aggregate;
+ var label = args.label;
+
+ var tforms = [];
+
+ for( var key in service.Transforms ) {
+ var obj = service.Transforms[key];
+ if( agg && !nonagg && !obj.aggregate ) continue;
+ if( !agg && nonagg && obj.aggregate ) continue;
+ if( !dtype && obj.datatype.length > 0 ) continue;
+ if( dtype && obj.datatype.length > 0 && transformIsForDatatype(key,dtype).length == 0 ) continue;
+ tforms.push(key);
+ }
+
+ return tforms;
+ }
+
+
+ service.transformIsForDatatype = function (tform, dtype) {
+ var obj = service.Transforms[tform];
+ return obj.datateype.filter(function(d) { return (d == dtype) })[0];
+ }
+
+ return service;
+}])
+;
+
--- /dev/null
+/*
+ * Report template builder
+ */
+
+angular.module('egReporter',
+ ['ngRoute', 'ui.bootstrap', 'egCoreMod', 'egUiMod', 'egGridMod', 'egReportMod', 'treeControl', 'ngToast'])
+
+.config(['ngToastProvider', function(ngToastProvider) {
+ ngToastProvider.configure({
+ verticalPosition: 'bottom',
+ animation: 'fade'
+ });
+}])
+
+.config(function($routeProvider, $locationProvider, $compileProvider) {
+ $locationProvider.html5Mode(true);
+ $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|blob):/); // grid export
+
+ var resolver = {delay : function(egStartup) {return egStartup.go()}};
+
+ $routeProvider.when('/reporter/template/clone/:folder/:id', {
+ templateUrl: './reporter/t_edit_template',
+ controller: 'ReporterTemplateEdit',
+ resolve : resolver
+ });
+
+ $routeProvider.when('/reporter/legacy/template/clone/:folder/:id', {
+ templateUrl: './reporter/t_legacy',
+ controller: 'ReporterTemplateLegacy',
+ resolve : resolver
+ });
+
+ $routeProvider.when('/reporter/template/new/:folder', {
+ templateUrl: './reporter/t_edit_template',
+ controller: 'ReporterTemplateEdit',
+ resolve : resolver
+ });
+
+ $routeProvider.when('/reporter/legacy/main', {
+ templateUrl: './reporter/t_legacy',
+ controller: 'ReporterTemplateLegacy',
+ resolve : resolver
+ });
+
+ // default page
+ $routeProvider.otherwise({redirectTo : '/reporter/legacy/main'});
+})
+
+/**
+ * controller for legacy template stuff
+ */
+.controller('ReporterTemplateLegacy',
+ ['$scope','$routeParams','$location','egCore',
+function($scope , $routeParams , $location , egCore) {
+
+ var template_id = $routeParams.id;
+ var folder_id = $routeParams.folder;
+
+ $scope.rurl = '/reports/oils_rpt.xhtml?ses=' + egCore.auth.token();
+
+ if (folder_id) {
+ $scope.rurl = '/reports/oils_rpt_builder.xhtml?ses=' +
+ egCore.auth.token() + '&folder=' + folder_id;
+
+ if (template_id) $scope.rurl += '&ct=' + template_id;
+ }
+
+}])
+
+/**
+ * Uber-controller for template editing
+ */
+.controller('ReporterTemplateEdit',
+ ['$scope','$q','$routeParams','$location','$timeout','$window','egCore','$uibModal','egPromptDialog',
+ 'egGridDataProvider','egReportTemplateSvc','$uibModal','egConfirmDialog','egSelectDialog','ngToast',
+function($scope , $q , $routeParams , $location , $timeout , $window, egCore , $uibModal , egPromptDialog ,
+ egGridDataProvider , egReportTemplateSvc , $uibModal , egConfirmDialog , egSelectDialog , ngToast) {
+
+ function values(o) { return Object.keys(o).map(function(k){return o[k]}) };
+
+ var template_id = $routeParams.id;
+ var folder_id = $routeParams.folder;
+
+ $scope.grid_display_fields_provider = egGridDataProvider.instance({
+ get : function (offset, count) {
+ return this.arrayNotifier(egReportTemplateSvc.display_fields, offset, count);
+ }
+ });
+ $scope.grid_filter_fields_provider = egGridDataProvider.instance({
+ get : function (offset, count) {
+ return this.arrayNotifier(egReportTemplateSvc.filter_fields, offset, count);
+ }
+ });
+
+ var dgrid = $scope.display_grid_controls = {};
+ var fgrid = $scope.filter_grid_controls = {};
+
+ var default_filter_obj = {
+ op : '=',
+ label : egReportTemplateSvc.Filters['='].label
+ };
+
+ var default_transform_obj = {
+ transform : 'Bare',
+ label : egReportTemplateSvc.Transforms.Bare.label,
+ aggregate : false
+ };
+
+ function mergePaths (items) {
+ var tree = {};
+
+ items.forEach(function (item) {
+ var t = tree;
+ var join_path = '';
+
+ item.path.forEach(function (p, i, a) {
+ var alias; // unpredictable hashes are fine for intermediate tables
+
+ if (i) { // not at the top of the tree
+ if (i == 1) join_path = join_path.split('-')[0];
+
+ var uplink = p.uplink.name;
+ join_path += '-' + uplink;
+ alias = hex_md5(join_path);
+
+ var uplink_alias = uplink + '-' + alias;
+
+ if (!t.join) t.join = {};
+ if (!t.join[uplink_alias]) t.join[uplink_alias] = {};
+
+ t = t.join[uplink_alias];
+
+ var djtype = 'inner';
+ if (p.uplink.reltype != 'has_a') djtype = 'left';
+
+ t.type = p.jtype || djtype;
+ t.key = p.uplink.key;
+ } else {
+ join_path = p.classname + '-' + p.classname;
+ alias = hex_md5(join_path);
+ }
+
+ if (!t.alias) t.alias = alias;
+ t.path = join_path;
+
+ t.table = p.struct.source ? p.struct.source : p.table;
+ t.idlclass = p.classname;
+
+ if (a.length == i + 1) { // end of the path array, need a predictable hash
+ t.label = item.path_label;
+ t.alias = hex_md5(item.path_label);
+ }
+
+ });
+ });
+
+ return tree;
+ }
+
+ $scope.constructTemplate = function () {
+ var param_counter = 0;
+ return {
+ version : 5,
+ doc_url : $scope.templateDocURL,
+ core_class : egCore.idl.classTree.top.classname,
+ select : dgrid.allItems().map(function (i) {
+ return {
+ alias : i.label,
+ path : i.path[i.path.length - 1].classname + '-' + i.name,
+ field_doc : i.doc_text,
+ relation : hex_md5(i.path_label),
+ column : {
+ colname : i.name,
+ transform : i.transform ? i.transform.transform : '',
+ transform_label : i.transform ? i.transform.label : '',
+ aggregate : !!i.transform.aggregate
+ }
+ }
+ }),
+ from : mergePaths( dgrid.allItems().concat(fgrid.allItems()) ),
+ where : fgrid.allItems().filter(function(i) {
+ return !i.transform.aggregate;
+ }).map(function (i) {
+ var cond = {};
+ if (
+ i.operator.op == 'is' ||
+ i.operator.op == 'is not' ||
+ i.operator.op == 'is blank' ||
+ i.operator.op == 'is not blank'
+ ) {
+ cond[i.operator.op] = null;
+ } else {
+ if (i.value === undefined) {
+ cond[i.operator.op] = '::P' + param_counter++;
+ }else {
+ cond[i.operator.op] = i.value;
+ }
+ }
+ return {
+ alias : i.label,
+ path : i.path[i.path.length - 1].classname + '-' + i.name,
+ field_doc : i.doc_text,
+ relation : hex_md5(i.path_label),
+ column : {
+ colname : i.name,
+ transform : i.transform.transform,
+ transform_label : i.transform.label,
+ aggregate : 0
+ },
+ condition : cond // constructed above
+ }
+ }),
+ having : fgrid.allItems().filter(function(i) {
+ return !!i.transform.aggregate;
+ }).map(function (i) {
+ var cond = {};
+ cond[i.operator.op] = '::P' + param_counter++;
+ return {
+ alias : i.label,
+ path : i.path[i.path.length - 1].classname + '-' + i.name,
+ field_doc : i.doc_text,
+ relation : hex_md5(i.path_label),
+ column : {
+ colname : i.name,
+ transform : i.transform.transform,
+ transform_label : i.transform.label,
+ aggregate : 1
+ },
+ condition : cond // constructed above
+ }
+ }),
+ display_cols: angular.copy( dgrid.allItems() ).map(strip_item),
+ filter_cols : angular.copy( fgrid.allItems() ).map(strip_item)
+ };
+
+ function strip_item (i) {
+ delete i.children;
+ i.path.forEach(function(p){
+ delete p.children;
+ delete p.fields;
+ delete p.links;
+ delete p.struct.permacrud;
+ delete p.struct.field_map;
+ delete p.struct.fields;
+ });
+ return i;
+ }
+
+ }
+
+ function loadTemplate () {
+ if (!template_id) return;
+ egCore.pcrud.retrieve( 'rt', template_id)
+ .then( function(template) {
+ template.data = angular.fromJson(template.data());
+ if (template.data.version < 5) { // redirect to old editor...
+ $window.location.href = egCore.env.basePath + 'reporter/legacy/template/clone/'+folder_id + '/' + template_id;
+ // } else if (template.data.version < 5) { // redirect to old editor...
+ } else {
+ $scope.templateName = template.name() + ' (clone)';
+ $scope.templateDescription = template.description();
+ $scope.templateDocURL = template.data.doc_url;
+
+ $scope.changeCoreSource( template.data.core_class );
+
+ egReportTemplateSvc.display_fields = template.data.display_cols;
+ egReportTemplateSvc.filter_fields = template.data.filter_cols;
+
+ $timeout(function(){
+ dgrid.refresh();
+ fgrid.refresh();
+ });
+ }
+ });
+
+ }
+
+ $scope.saveTemplate = function () {
+ var tmpl = new egCore.idl.rt();
+ tmpl.name( $scope.templateName );
+ tmpl.description( $scope.templateDescription );
+ tmpl.owner(egCore.auth.user().id());
+ tmpl.folder(folder_id);
+ tmpl.data(angular.toJson($scope.constructTemplate()));
+
+ egConfirmDialog.open(tmpl.name(), egCore.strings.TEMPLATE_CONF_CONFIRM_SAVE,
+ {ok : function() {
+ return egCore.pcrud.create( tmpl )
+ .then(
+ function() {
+ ngToast.create(egCore.strings.TEMPLATE_CONF_SUCCESS_SAVE);
+ return $timeout(
+ function(){
+ $window.location.href = egCore.env.basePath + 'reporter/legacy/main';
+ },
+ 1000
+ );
+ },
+ function() {
+ ngToast.warning(egCore.strings.TEMPLATE_CONF_FAIL_SAVE);
+ }
+ );
+ }}
+ );
+ }
+
+ $scope.addDisplayFields = function () {
+ var t = $scope.selected_transform;
+ if (!t) t = default_transform_obj;
+
+ egReportTemplateSvc.addFields(
+ 'display_fields',
+ $scope.selected_source_field_list,
+ t,
+ $scope.currentPathLabel,
+ $scope.currentPath
+ );
+ dgrid.refresh();
+ }
+
+ $scope.addFilterFields = function () {
+ var t = $scope.selected_transform;
+ if (!t) t = default_transform_obj;
+ f = default_filter_obj;
+
+ egReportTemplateSvc.addFields(
+ 'filter_fields',
+ $scope.selected_source_field_list,
+ t,
+ $scope.currentPathLabel,
+ $scope.currentPath,
+ f
+ );
+ fgrid.refresh();
+ }
+
+ $scope.moveDisplayFieldUp = function (items) {
+ items.reverse().forEach(function(item) {
+ egReportTemplateSvc.moveFieldUp('display_fields', item);
+ });
+ dgrid.refresh();
+ }
+
+ $scope.moveDisplayFieldDown = function (items) {
+ items.forEach(function(item) {
+ egReportTemplateSvc.moveFieldDown('display_fields', item);
+ });
+ dgrid.refresh();
+ }
+
+ $scope.removeDisplayField = function (items) {
+ items.forEach(function(item) {egReportTemplateSvc.removeField('display_fields', item)});
+ dgrid.refresh();
+ }
+
+ $scope.changeDisplayLabel = function (items) {
+ items.forEach(function(item) {
+ egPromptDialog.open(egCore.strings.TEMPLATE_CONF_PROMPT_CHANGE, item.label || '',
+ {ok : function(value) {
+ if (value) egReportTemplateSvc.display_fields[item.index].label = value;
+ }}
+ );
+ });
+ dgrid.refresh();
+ }
+
+ $scope.changeDisplayFieldDoc = function (items) {
+ items.forEach(function(item) {
+ egPromptDialog.open(egCore.strings.TEMPLATE_FIELD_DOC_PROMPT_CHANGE, item.doc_text || '',
+ {ok : function(value) {
+ if (value) egReportTemplateSvc.display_fields[item.index].doc_text = value;
+ }}
+ );
+ });
+ dgrid.refresh();
+ }
+
+ $scope.changeFilterFieldDoc = function (items) {
+ items.forEach(function(item) {
+ egPromptDialog.open(egCore.strings.TEMPLATE_FIELD_DOC_PROMPT_CHANGE, item.doc_text || '',
+ {ok : function(value) {
+ if (value) egReportTemplateSvc.filter_fields[item.index].doc_text = value;
+ }}
+ );
+ });
+ fgrid.refresh();
+ }
+
+ $scope.changeFilterValue = function (items) {
+ items.forEach(function(item) {
+ var l = null;
+ egPromptDialog.open(egCore.strings.TEMPLATE_CONF_DEFAULT, item.value || '',
+ {ok : function(value) {
+ if (value) egReportTemplateSvc.filter_fields[item.index].value = value;
+ }}
+ );
+ });
+ fgrid.refresh();
+ }
+
+ $scope.changeTransform = function (items) {
+
+ var f = items[0];
+
+ var tlist = [];
+ angular.forEach(egReportTemplateSvc.Transforms, function (o,n) {
+ if ( o.datatype.indexOf(f.datatype) > -1) {
+ if (tlist.indexOf(o.label) == -1) tlist.push( o.label );
+ }
+ });
+
+ items.forEach(function(item) {
+ egSelectDialog.open(
+ egCore.strings.SELECT_TFORM, tlist, item.transform.label,
+ {ok : function(value) {
+ if (value) {
+ var t = egReportTemplateSvc.getTransformByLabel(value);
+ item.transform = {
+ label : value,
+ transform : t,
+ aggregate : egReportTemplateSvc.Transforms[t].aggregate ? true : false
+ };
+ }
+ }}
+ );
+ });
+
+ fgrid.refresh();
+ }
+
+ $scope.changeOperator = function (items) {
+
+ var flist = [];
+ Object.keys(egReportTemplateSvc.Filters).forEach(function(k){
+ var v = egReportTemplateSvc.Filters[k];
+ if (flist.indexOf(v.label) == -1) flist.push(v.label);
+ if (v.labels && v.labels.length > 0) {
+ v.labels.forEach(function(l) {
+ if (flist.indexOf(l) == -1) flist.push(l);
+ })
+ }
+ });
+
+ items.forEach(function(item) {
+ var l = item.operator ? item.operator.label : '';
+ egSelectDialog.open(
+ egCore.strings.SELECT_OP, flist, l,
+ {ok : function(value) {
+ if (value) {
+ var t = egReportTemplateSvc.getFilterByLabel(value);
+ item.operator = { label: value, op : t };
+ }
+ }}
+ );
+ });
+
+ fgrid.refresh();
+ }
+
+ $scope.removeFilterValue = function (items) {
+ items.forEach(function(item) {delete egReportTemplateSvc.filter_fields[item.index].value});
+ fgrid.refresh();
+ }
+
+ $scope.removeFilterField = function (items) {
+ items.forEach(function(item) {egReportTemplateSvc.removeField('filter_fields', item)});
+ fgrid.refresh();
+ }
+
+ $scope.allSources = values(egCore.idl.classes).sort( function(a,b) {
+ if (a.core && !b.core) return -1;
+ if (b.core && !a.core) return 1;
+ aname = a.label ? a.label : a.name;
+ bname = b.label ? b.label : b.name;
+ if (aname > bname) return 1;
+ return -1;
+ });
+
+ $scope.class_tree = [];
+ $scope.selected_source = null;
+ $scope.selected_source_fields = [];
+ $scope.selected_source_field_list = [];
+ $scope.available_field_transforms = [];
+ $scope.coreSource = null;
+ $scope.coreSourceChosen = false;
+ $scope.currentPathLabel = '';
+
+ $scope.treeExpand = function (node, expanding) {
+ if (expanding) node.children.map(egCore.idl.classTree.fleshNode);
+ }
+
+ $scope.filterFields = function (n) {
+ return n.virtual ? false : true;
+ // should we hide links?
+ return n.datatype && n.datatype != 'link'
+ }
+
+ $scope.field_tree_opts = {
+ multiSelection: true,
+ equality : function(node1, node2) {
+ return node1.name == node2.name;
+ }
+ }
+
+ $scope.field_transforms_tree_opts = {
+ equality : function(node1, node2) {
+ if (!node2) return false;
+ return node1.transform == node2.transform;
+ }
+ }
+
+ $scope.selectFields = function () {
+ while ($scope.available_field_transforms.length) {
+ $scope.available_field_transforms.pop();
+ }
+
+ angular.forEach( $scope.selected_source_field_list, function (f) {
+ angular.forEach(egReportTemplateSvc.Transforms, function (o,n) {
+ if ( o.datatype.indexOf(f.datatype) > -1) {
+ var include = true;
+
+ angular.forEach($scope.available_field_transforms, function (t) {
+ if (t.transform == n) include = false;
+ });
+
+ if (include) $scope.available_field_transforms.push({
+ transform : n,
+ label : o.label,
+ aggregate : o.aggregate ? true : false
+ });
+ }
+ });
+ });
+
+ }
+
+ $scope.selectSource = function (node, selected, $path) {
+
+ while ($scope.selected_source_field_list.length) {
+ $scope.selected_source_field_list.pop();
+ }
+ while ($scope.selected_source_fields.length) {
+ $scope.selected_source_fields.pop();
+ }
+
+ if (selected) {
+ $scope.currentPath = angular.copy( $path().reverse() );
+ $scope.selected_source = node;
+ $scope.currentPathLabel = $scope.currentPath.map(function(n,i){
+ var l = n.label
+ if (i) l += ' (' + n.jtype + ')';
+ return l;
+ }).join( ' -> ' );
+ angular.forEach( node.fields, function (f) {
+ $scope.selected_source_fields.push( f );
+ });
+ } else {
+ $scope.currentPathLabel = '';
+ }
+
+ // console.log($scope.selected_source);
+ }
+
+ $scope.changeCoreSource = function (new_core) {
+ console.log('changeCoreSource: '+new_core);
+ function change_core () {
+ if (new_core) $scope.coreSource = new_core;
+ $scope.coreSourceChosen = true;
+
+ $scope.class_tree.pop();
+ $scope.class_tree.push(
+ egCore.idl.classTree.setTop($scope.coreSource)
+ );
+
+ while ($scope.selected_source_fields.length) {
+ $scope.selected_source_fields.pop();
+ }
+
+ while ($scope.available_field_transforms.length) {
+ $scope.available_field_transforms.pop();
+ }
+
+ $scope.currentPathLabel = '';
+ }
+
+ if ($scope.coreSourceChosen) {
+ egConfirmDialog.open(
+ egCore.strings.FOLDERS_TEMPLATE,
+ egCore.strings.SOURCE_SETUP_CONFIRM_EXIT,
+ {ok : change_core}
+ );
+ } else {
+ change_core();
+ }
+ }
+
+ loadTemplate();
+}])
+
+;
*/
function mkclass(cls, fields) {
+ service.classes[cls].core_label = service.classes[cls].core ? 'Core sources' : 'Non-core sources';
+ service.classes[cls].classname = cls;
+
service[cls] = function(seed) {
this.a = seed || [];
this.classname = cls;
return hash;
}
- return service;
-}]);
+ // Using IDL links, allow construction of a tree-ish data structure from
+ // the IDL2js web service output. This structure will be directly usable
+ // by the <treecontrol> directive
+ service.classTree = {
+ top : null
+ };
+
+ function _sort_class_fields (a,b) {
+ var aname = a.label || a.name;
+ var bname = b.label || b.name;
+ return aname > bname ? 1 : -1;
+ }
+
+ service.classTree.buildNode = function (cls, args) {
+ if (!cls) return null;
+
+ var n = service.classes[cls];
+ if (!n) return null;
+
+ if (!args)
+ args = { label : n.label };
+
+ args.id = cls;
+ if (args.from)
+ args.id = args.from + '.' + args.id;
+
+ return angular.extend( args, {
+ idl : service[cls],
+ jtype : 'inner',
+ uplink : args.link,
+ classname: cls,
+ struct : n,
+ table : n.table,
+ fields : n.fields.sort( _sort_class_fields ),
+ links : n.fields
+ .filter( function(x) { return x.type == 'link'; } )
+ .sort( _sort_class_fields ),
+ children: []
+ });
+ }
+
+ service.classTree.fleshNode = function ( node ) {
+ if (node.children.length > 0)
+ return node; // already done already
+ angular.forEach(
+ node.links.sort( _sort_class_fields ),
+ function (n) {
+ var nlabel = n.label ? n.label : n.name;
+ node.children.push(
+ service.classTree.buildNode(
+ n["class"],
+ { label : nlabel,
+ from : node.id,
+ link : n
+ }
+ )
+ );
+ }
+ );
+
+ return node;
+ }
+
+ service.classTree.setTop = function (cls) {
+ console.debug('setTop: '+cls);
+ return service.classTree.top = service.classTree.fleshNode(
+ service.classTree.buildNode(cls)
+ );
+ }
+
+ service.classTree.getTop = function () {
+ return service.classTree.top;
+ }
+
+ return service;
+}])
+;
}])
/**
+ * egSelectDialog.open(
+ * "message goes {{here}}",
+ * list, // ['values','for','dropdown'],
+ * selectedValue, // optional
+ * {
+ * here : 'foo',
+ * ok : function(value) {console.log(value)},
+ * cancel : function() {console.log('prompt denied')}
+ * }
+ * );
+ */
+.factory('egSelectDialog',
+
+ ['$uibModal','$interpolate',
+function($uibModal, $interpolate) {
+ var service = {};
+
+ service.open = function(message, inputList, selectedValue, msg_scope) {
+ return $uibModal.open({
+ templateUrl: './share/t_select_dialog',
+ controller: ['$scope', '$uibModalInstance',
+ function($scope, $uibModalInstance) {
+ $scope.message = $interpolate(message)(msg_scope);
+ $scope.args = {
+ list : inputList,
+ value : selectedValue
+ };
+ $scope.focus = true;
+ $scope.ok = function() {
+ if (msg_scope.ok) msg_scope.ok($scope.args.value);
+ $uibModalInstance.close()
+ }
+ $scope.cancel = function() {
+ if (msg_scope.cancel) msg_scope.cancel();
+ $uibModalInstance.dismiss();
+ }
+ }
+ ]
+ })
+ }
+
+ return service;
+}])
+
+/**
* Warn on page unload and give the user a chance to avoid navigating
* away from the current page.
* Only one handler is supported per page.
scope: {
list: "=", // list of strings
selected: "=",
- egDisabled: "="
+ egDisabled: "=",
+ allowAll: "@",
},
template:
'<div class="input-group">'+
'<button type="button" ng-click="showAll()" class="btn btn-default dropdown-toggle"><span class="caret"></span></button>'+
'<ul class="dropdown-menu dropdown-menu-right">'+
'<li ng-repeat="item in list|filter:selected"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
- '<li ng-if="all" class="divider"><span></span></li>'+
- '<li ng-if="all" ng-repeat="item in list"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
+ '<li ng-if="complete_list" class="divider"><span></span></li>'+
+ '<li ng-if="complete_list" ng-repeat="item in list"><a href ng-click="changeValue(item)">{{item}}</a></li>'+
'</ul>'+
'</div>'+
'</div>',
controller: ['$scope','$filter',
function( $scope , $filter) {
- $scope.all = false;
+ $scope.complete_list = false;
$scope.isopen = false;
+ $scope.clickedopen = false;
+ $scope.clickedclosed = null;
$scope.showAll = function () {
- if ($scope.selected.length > 0)
- $scope.all = true;
+
+ $scope.clickedopen = !$scope.clickedopen;
+
+ if ($scope.clickedclosed === null) {
+ if (!$scope.clickedopen) {
+ $scope.clickedclosed = true;
+ }
+ } else {
+ $scope.clickedclosed = !$scope.clickedopen;
+ }
+
+ if ($scope.selected.length > 0) $scope.complete_list = true;
+ if ($scope.selected.length == 0) $scope.complete_list = false;
+ $scope.makeOpen();
}
$scope.makeOpen = function () {
- $scope.isopen = $filter('filter')(
+ $scope.isopen = $scope.clickedopen || ($filter('filter')(
$scope.list,
$scope.selected
- ).length > 0 && $scope.selected.length > 0;
- $scope.all = false;
+ ).length > 0 && $scope.selected.length > 0);
+ if ($scope.clickedclosed) $scope.isopen = false;
}
$scope.changeValue = function (newVal) {
$scope.selected = newVal;
$scope.isopen = false;
+ $scope.clickedclosed = null;
+ $scope.clickedopen = false;
+ if ($scope.selected.length == 0) $scope.complete_list = false;
}
}
</xsl:choose>
</xsl:template>
- <xsl:template match="idl:class"><xsl:value-of select="@id"/>:{name:"<xsl:value-of select="@id"/>",<xsl:if test="@reporter:label">label:"<xsl:value-of select="@reporter:label"/>",</xsl:if><xsl:if test="@oils_persist:restrict_primary">restrict_primary:"<xsl:value-of select="@oils_persist:restrict_primary"/>",</xsl:if><xsl:if test="@oils_persist:virtual = 'true'">virtual:true,</xsl:if><xsl:if test="idl:fields/@oils_persist:primary">pkey:"<xsl:value-of select="idl:fields/@oils_persist:primary"/>",</xsl:if><xsl:if test="idl:fields/@oils_persist:sequence">pkey_sequence:"<xsl:value-of select="idl:fields/@oils_persist:sequence"/>",</xsl:if><xsl:apply-templates select="idl:fields"/><xsl:apply-templates select="permacrud:permacrud"/>}</xsl:template>
+ <xsl:template match="idl:class"><xsl:value-of select="@id"/>:{name:"<xsl:value-of select="@id"/>",<xsl:if test="@reporter:label">label:"<xsl:value-of select="@reporter:label"/>",</xsl:if><xsl:if test="@oils_persist:restrict_primary">restrict_primary:"<xsl:value-of select="@oils_persist:restrict_primary"/>",</xsl:if><xsl:if test="@oils_persist:tablename">table:"<xsl:value-of select="@oils_persist:tablename"/>",</xsl:if><xsl:if test="@reporter:core = 'true'">core:true,</xsl:if><xsl:if test="@oils_persist:virtual = 'true'">virtual:true,</xsl:if><xsl:if test="oils_persist:source_definition">source:"(<xsl:value-of select="oils_persist:source_definition/text()"/>)",</xsl:if><xsl:if test="idl:fields/@oils_persist:primary">pkey:"<xsl:value-of select="idl:fields/@oils_persist:primary"/>",</xsl:if><xsl:if test="idl:fields/@oils_persist:sequence">pkey_sequence:"<xsl:value-of select="idl:fields/@oils_persist:sequence"/>",</xsl:if><xsl:apply-templates select="idl:fields"/><xsl:apply-templates select="permacrud:permacrud"/>}</xsl:template>
<xsl:template match="idl:fields">fields:[<xsl:for-each select="idl:field"><xsl:call-template name="printField"><xsl:with-param name='pos' select="position()"/></xsl:call-template><xsl:if test="not(position() = last())">,</xsl:if></xsl:for-each>]</xsl:template>