<commit_before>var async = require('async');
var _ = require('@sailshq/lodash');

module.exports = function(self, options) {
  // Each doc-type has an array of fields which will be updated
  // if the document is moved to the trash. In most cases 'slug'
  // might suffice. For users, for instance, the email field should
  // be prefixed (de-duplicated) so that the email address is available.

  self.trashPrefixFields = [ 'slug' ];

  self.addTrashPrefixFields = function(fields) {
    self.trashPrefixFields = self.trashPrefixFields.concat(fields);
  };

  // After moving document to the trash, prefix any fields that have
  // unique indexes so that other pieces are allowed to reuse
  // those usernames, email addresses, etc.

  self.deduplicateTrash = function(req, doc, callback) {
    var prefix = 'deduplicate-' + doc._id + '-';
    var $set = {};

    _.each(self.trashPrefixFields, function(name) {
      if ((typeof doc[name]) !== 'string') {
        // Presumably a sparse index
        return;
      }
      if (doc[name].substr(0, prefix.length) !== prefix) {
        $set[name] = prefix + doc[name];
      }
      // So methods called later, or extending this method, see the change in piece
      doc[name] = $set[name];
    });
    $set.trash = true;
    return self.apos.docs.db.update({ _id: doc._id }, { $set: $set }, callback);
  };

  // When rescuing docs from the trash, attempt to un-prefix any fields
  // that have unique indexes. However, if we then find it's been taken
  // in the meanwhile, leave the prefix in place and leave it up to
  // the user to resolve the issue.

  self.deduplicateRescue = function(req, doc, callback) {
    var prefix = 'deduplicate-' + doc._id + '-';
    var $set = {};
    _.each(self.trashPrefixFields, function(name) {
      if ((typeof doc[name]) !== 'string') {
        // Presumably a sparse index
        return;
      }
      $set[name] = doc[name].replace(prefix, '');
    });
    return async.series([
      checkDuplicates,
      update
    ], callback);
    function checkDuplicates(callback) {
      return async.eachSeries(self.trashPrefixFields, checkOne, callback);
    }
    function update(callback) {
      var action = {
        $set: $set
      };
      // So methods called later, or extending this method, see the change in docs
      _.assign(doc, $set);
      if (_.isEmpty($set)) {
        // Nothing to do
        return setImmediate(callback);
      }
      return self.apos.docs.db.update({ _id: doc._id }, action, callback);
    }
    function checkOne(name, callback) {
      var query = { type: self.name, _id: { $ne: doc._id } };
      query[name] = $set[name];
      if ($set[name] === '') {
        // Assume sparse index if empty strings are seen; don't
        // generate lots of weird prefix-only email addresses
        return setImmediate(callback);
      }
      return self.apos.docs.db.findOne(query, { _id: 1 }, function(err, found) {
        if (err) {
          return callback(err);
        }
        if (found) {
          delete $set[name];
        }
        return callback(null);
      });
    }
  };

  // Returns the minimum permission name that should be checked for
  // to determine if this user has some edit privileges for
  // this doc type (not necessarily every instance of it),
  // for example the ability to create one. Determines
  // admin bar menu item visibility

  self.getEditPermissionName = function() {
    return self.isAdminOnly() ? 'admin' : ('edit-' + self.name);
  };

  // Returns the minimum permission name that should be checked for
  // to determine if this user has full admin privileges for
  // this doc type

  self.getAdminPermissionName = function() {
    return self.isAdminOnly() ? 'admin' : ('admin-' + self.name);
  };

  // Define the related type "cursor", so that all of our subclasses
  // automatically have a cursor type too, and it is autoloaded from
  // ./lib/cursor.js if that exists, otherwise given an empty
  // definition.

  self.defineCursor = function() {
    self.defineRelatedType('cursor', {
      stop: 'apostrophe-doc-type-manager'
    });
  };

  // Returns a cursor that will only yield docs of the appropriate type
  // as determined by the `name` option of the module. Returns a cursor of
  // the appropriate type for the current module, even if it is a subclass.

  // Returns a cursor for use in finding docs. See cursor.js for chainable
  // filters, and also yielders that actually deliver the docs to you.

  self.find = function(req, criteria, projection) {
    return self.createRelatedType('cursor', {
      apos: self.apos,
      module: self,
      req: req,
      criteria: criteria,
      projection: projection
    });
  };

  // Returns a new instance of the doc type, with the appropriate default
  // values for each schema field.

  self.newInstance = function() {
    var doc = self.apos.schemas.newInstance(self.schema);
    doc.type = self.name;
    return doc;
  };

  // Returns a MongoDB projection object to be used when querying
  // for this type if all that is needed is a title for display
  // in an autocomplete menu. Default behavior is to
  // return only the `title`, `_id` and `slug` properties.
  // Removing any of these three is not recommended.
  //
  // `query.field` will contain the schema field definition for
  // the join the user is attempting to match titles from.

  self.getAutocompleteProjection = function(query) {
    return {
      title: 1,
      _id: 1,
      slug: 1
    };
  };

  // Returns a string to represent the given `doc` in an
  // autocomplete menu. `doc` will contain only the fields returned
  // by `getAutocompleteProjection`. `query.field` will contain
  // the schema field definition for the join the user is attempting
  // to match titles from. The default behavior is to return
  // the `title` property. This is sometimes extended to include
  // event start dates and similar information that helps the
  // user distinguish between docs.

  self.getAutocompleteTitle = function(doc, query) {
    return doc.title;
  };

  // Used by `apostrophe-versions` to label changes that
  // are made to joins by ID. Set `change.text` to the
  // desired text.

  self.decorateChange = function(doc, change) {
    change.text = doc.title;
  };

  // Returns true if only admins are allowed to edit this type.
  // Respected by the pieces module when deciding whether to
  // enumerate more specific permissions as choices for this
  // module.

  self.isAdminOnly = function() {
    return false;
  };

  // Return a new schema containing only fields for which the
  // current user has the permission specified by the `permission`
  // property of the schema field, or there is no `permission` property for the field.

  self.allowedSchema = function(req) {
    var disabled;
    var type;
    var schema = _.filter(self.schema, function(field) {
      return (!field.permission) || self.apos.permissions.can(req, field.permission);
    });
    var typeIndex = _.findIndex(schema, { name: 'type' });
    if (typeIndex !== -1) {
      // This option exists so that the
      // apostrophe-option-overrides and apostrophe-workflow modules,
      // if present, can be used together to disable various types based
      // on locale settings
      disabled = self.apos.pages.getOption(req, 'disabledTypes');
      if (disabled) {
        // Take care to clone so we don't wind up modifying
        // the original schema, the allowed schema is only
        // a shallow clone of the array so far
        type = _.cloneDeep(schema[typeIndex]);
        type.choices = _.filter(type.choices, function(choice) {
          return !_.contains(disabled, choice.value);
        });
        // Make sure the allowed schema refers to the clone,
        // not the original
        schema[typeIndex] = type;
      }
    }
    return schema;
  };

  self.composeSchema = function() {

    // If a type is adminOnly remove the fields relating to permissions editing
    if (self.isAdminOnly()) {
      options.removeFields = (options.removeFields || []).concat([
        'loginRequired',
        '_viewUsers',
        '_viewGroups',
        '_editUsers',
        '_editGroups'
      ]);
    }

    self.schema = self.apos.schemas.compose(self.options);

    // Extend `composeSchema` to flag the use of field names
    // that are forbidden or nonfunctional in all doc types, i.e.
    // properties that will be overwritten by non-schema-driven
    // logic, such as `_id` or `docPermissions`

    const forbiddenFields = [ '_id', 'docPermissions', 'titleSortified', 'highSearchText', 'highSearchWords', 'lowSearchText', 'searchSummary' ];
    _.each(self.schema, function(field) {
      if (_.contains(forbiddenFields, field.name)) {
        throw new Error('Doc type ' + self.name + ': the field name ' + field.name + ' is forbidden');
      }
    });

    self.patchAdminPermissionInSchema();

  };

  // In the schema, `_editUsers` and `_editGroups` are
  // initially locked down to sitewide admins. Now that
  // we've constructed the module completely, take advantage
  // of `getAdminPermissionName` to specify a more nuanced permission,
  // such as the admin permission for this piece type, or for pages

  self.patchAdminPermissionInSchema = function() {
    var fieldNames = [ '_editUsers', '_editGroups' ];
    _.each(fieldNames, function(fieldName) {
      var field = _.find(self.schema, { name: fieldName });
      if (field) {
        field.permission = self.getAdminPermissionName();
      }
    });
  };

  // This method provides the back end of /autocomplete routes.
  // For the implementation of the autocomplete() filter see autocomplete.js.
  //
  // "query" must contain a "field" property which is the schema join field
  // that describes the relationship we're adding items to.
  //
  // "query" must also contain a "term" property, which is a partial
  // string to be autocompleted; otherwise an empty array is delivered
  // to the callback.
  //
  // We don't launder the input here, see the 'autocomplete' route.

  self.autocomplete = function(req, query, callback) {
    var cursor = self.find(req, {}, {}).sort('search');
    if (self.extendAutocompleteCursor) {
      self.extendAutocompleteCursor(cursor);
    }

    cursor.projection(self.getAutocompleteProjection(), query);

    // Try harder not to call autocomplete with something that doesn't
    // result in a search
    if (query.term && query.term.toString && query.term.toString().length) {
      var term = self.apos.launder.string(query.term);
      cursor.autocomplete(term);
    } else {
      return callback(null, []);
    }

    if (!(query.filters && query.filters.limit)) {
      cursor.limit(10);
    }

    // This is the big payoff of blessing join fields: we can apply their
    // filters in API calls like this one, knowing that the field definition
    // the browser sent us is one the developer created
    cursor.queryToFilters(query.field.filters || {});

    // Format it as value & id properties for compatibility with jquery UI autocomplete
    cursor.toArray(function(err, docs) {
      if (err) {
        return callback(err);
      }
      // Put the snippets in id order
      if (query.values) {
        docs = self.apos.utils.orderById(query.values, docs);
      }
      var results = _.map(docs, function(doc) {
        var response = {
          // jquery ui autocomplete demands label and value
          label: self.getAutocompleteTitle(doc, query),
          value: doc._id
        };
        _.defaults(response, _.omit(doc, 'title', '_id'));
        return response;
      });
      return callback(null, results);
    });
  };

  // Adds a listener for the `docSearchIndex` Apostrophe event, pointing to the
  // `searchIndexListener` method.

  self.addSearchIndexListener = function() {
    self.apos.on('docSearchIndex', self.searchIndexListener);
  };

  // Invoked when a `docSearchIndex` event takes place, this method adds
  // additional search texts to the `texts` array (modify it in place by
  // pushing new objects to it). These texts influence search results.
  // The default behavior is quite useful, so you won't often need to
  // override this.
  //
  // Each "text" is an *object* and must have at least `weight` and `text` properties.
  // If `weight` is >= 10, the text will be included in autocomplete searches and
  // given higher priority in full-text searches. Otherwise it will be included
  // only in full-text searches.
  //
  // If `silent` is `true`, the `searchSummary` property will not contain
  // the text.
  //
  // By default this method invokes `schemas.indexFields`, which will push
  // texts for all of the schema fields that support this unless they are
  // explicitly set `searchable: false`.
  //
  // In any case, the text of rich text widgets is always included as
  // lower-priority search text.

  self.searchIndexListener = function(doc, texts) {
    if (doc.type !== self.name) {
      return;
    }
    self.apos.schemas.indexFields(self.schema, doc, texts);
  };

  // Fields required to compute the `_url` property.
  // Used to implement a "projection" for `_url` if
  // requested by the developer
  self.getUrlFields = function() {
    return [ 'type', 'slug', 'tags' ];
  };

  // Override to modify `data` before it is passed to
  // the `chooserModal.html` template
  self.beforeChooserModal = function(req, data) {
  };

  // Most of the time, this is called for you. Any schema field
  // with `sortify: true` will automatically get a migration to
  // ensure that, if the field is named `lastName`, then
  // `lastNameSortified` exists.
  //
  // Adds a migration that takes the given field, such as `lastName`, and
  // creates a parallel `lastNameSortified` field, formatted with
  // `apos.utils.sortify` so that it sorts and compares in a more
  // intuitive, case-insensitive way.
  //
  // After adding such a migration, you can add `sortify: true` to the
  // schema field declaration for `field`, and any calls to
  // the `sort()` cursor filter for `lastName` will automatically
  // use `lastNameSortified`. You can also do that explicitly of course.
  //
  // Note that you want to do both things (add the migration, and
  // add `sortify: true`) because `sortify: true` guarantees that
  // `lastNameSortified` gets updated on all saves of a doc of this type.
  // The migration is a one-time fix for existing data.

  self.addSortifyMigration = function(field) {
    if (!self.name) {
      return;
    }
    return self.apos.migrations.addSortify(self.__meta.name, { type: self.name }, field);
  };

};
<commit_msg>Remove trash flag from doc-type-manager dedupe function<commit_after>var async = require('async');
var _ = require('@sailshq/lodash');

module.exports = function(self, options) {
  // Each doc-type has an array of fields which will be updated
  // if the document is moved to the trash. In most cases 'slug'
  // might suffice. For users, for instance, the email field should
  // be prefixed (de-duplicated) so that the email address is available.

  self.trashPrefixFields = [ 'slug' ];

  self.addTrashPrefixFields = function(fields) {
    self.trashPrefixFields = self.trashPrefixFields.concat(fields);
  };

  // prefix any fields that have
  // unique indexes so that other pieces are allowed to reuse
  // those usernames, email addresses, etc.

  self.deduplicateTrash = function(req, doc, callback) {
    var prefix = 'deduplicate-' + doc._id + '-';
    var $set = {};

    _.each(self.trashPrefixFields, function(name) {
      if ((typeof doc[name]) !== 'string') {
        // Presumably a sparse index
        return;
      }
      if (doc[name].substr(0, prefix.length) !== prefix) {
        $set[name] = prefix + doc[name];
      }
      // So methods called later, or extending this method, see the change in piece
      doc[name] = $set[name];
    });
    return self.apos.docs.db.update({ _id: doc._id }, { $set: $set }, callback);
  };

  // When rescuing docs from the trash, attempt to un-prefix any fields
  // that have unique indexes. However, if we then find it's been taken
  // in the meanwhile, leave the prefix in place and leave it up to
  // the user to resolve the issue.

  self.deduplicateRescue = function(req, doc, callback) {
    var prefix = 'deduplicate-' + doc._id + '-';
    var $set = {};
    _.each(self.trashPrefixFields, function(name) {
      if ((typeof doc[name]) !== 'string') {
        // Presumably a sparse index
        return;
      }
      $set[name] = doc[name].replace(prefix, '');
    });
    return async.series([
      checkDuplicates,
      update
    ], callback);
    function checkDuplicates(callback) {
      return async.eachSeries(self.trashPrefixFields, checkOne, callback);
    }
    function update(callback) {
      var action = {
        $set: $set
      };
      // So methods called later, or extending this method, see the change in docs
      _.assign(doc, $set);
      if (_.isEmpty($set)) {
        // Nothing to do
        return setImmediate(callback);
      }
      return self.apos.docs.db.update({ _id: doc._id }, action, callback);
    }
    function checkOne(name, callback) {
      var query = { type: self.name, _id: { $ne: doc._id } };
      query[name] = $set[name];
      if ($set[name] === '') {
        // Assume sparse index if empty strings are seen; don't
        // generate lots of weird prefix-only email addresses
        return setImmediate(callback);
      }
      return self.apos.docs.db.findOne(query, { _id: 1 }, function(err, found) {
        if (err) {
          return callback(err);
        }
        if (found) {
          delete $set[name];
        }
        return callback(null);
      });
    }
  };

  // Returns the minimum permission name that should be checked for
  // to determine if this user has some edit privileges for
  // this doc type (not necessarily every instance of it),
  // for example the ability to create one. Determines
  // admin bar menu item visibility

  self.getEditPermissionName = function() {
    return self.isAdminOnly() ? 'admin' : ('edit-' + self.name);
  };

  // Returns the minimum permission name that should be checked for
  // to determine if this user has full admin privileges for
  // this doc type

  self.getAdminPermissionName = function() {
    return self.isAdminOnly() ? 'admin' : ('admin-' + self.name);
  };

  // Define the related type "cursor", so that all of our subclasses
  // automatically have a cursor type too, and it is autoloaded from
  // ./lib/cursor.js if that exists, otherwise given an empty
  // definition.

  self.defineCursor = function() {
    self.defineRelatedType('cursor', {
      stop: 'apostrophe-doc-type-manager'
    });
  };

  // Returns a cursor that will only yield docs of the appropriate type
  // as determined by the `name` option of the module. Returns a cursor of
  // the appropriate type for the current module, even if it is a subclass.

  // Returns a cursor for use in finding docs. See cursor.js for chainable
  // filters, and also yielders that actually deliver the docs to you.

  self.find = function(req, criteria, projection) {
    return self.createRelatedType('cursor', {
      apos: self.apos,
      module: self,
      req: req,
      criteria: criteria,
      projection: projection
    });
  };

  // Returns a new instance of the doc type, with the appropriate default
  // values for each schema field.

  self.newInstance = function() {
    var doc = self.apos.schemas.newInstance(self.schema);
    doc.type = self.name;
    return doc;
  };

  // Returns a MongoDB projection object to be used when querying
  // for this type if all that is needed is a title for display
  // in an autocomplete menu. Default behavior is to
  // return only the `title`, `_id` and `slug` properties.
  // Removing any of these three is not recommended.
  //
  // `query.field` will contain the schema field definition for
  // the join the user is attempting to match titles from.

  self.getAutocompleteProjection = function(query) {
    return {
      title: 1,
      _id: 1,
      slug: 1
    };
  };

  // Returns a string to represent the given `doc` in an
  // autocomplete menu. `doc` will contain only the fields returned
  // by `getAutocompleteProjection`. `query.field` will contain
  // the schema field definition for the join the user is attempting
  // to match titles from. The default behavior is to return
  // the `title` property. This is sometimes extended to include
  // event start dates and similar information that helps the
  // user distinguish between docs.

  self.getAutocompleteTitle = function(doc, query) {
    return doc.title;
  };

  // Used by `apostrophe-versions` to label changes that
  // are made to joins by ID. Set `change.text` to the
  // desired text.

  self.decorateChange = function(doc, change) {
    change.text = doc.title;
  };

  // Returns true if only admins are allowed to edit this type.
  // Respected by the pieces module when deciding whether to
  // enumerate more specific permissions as choices for this
  // module.

  self.isAdminOnly = function() {
    return false;
  };

  // Return a new schema containing only fields for which the
  // current user has the permission specified by the `permission`
  // property of the schema field, or there is no `permission` property for the field.

  self.allowedSchema = function(req) {
    var disabled;
    var type;
    var schema = _.filter(self.schema, function(field) {
      return (!field.permission) || self.apos.permissions.can(req, field.permission);
    });
    var typeIndex = _.findIndex(schema, { name: 'type' });
    if (typeIndex !== -1) {
      // This option exists so that the
      // apostrophe-option-overrides and apostrophe-workflow modules,
      // if present, can be used together to disable various types based
      // on locale settings
      disabled = self.apos.pages.getOption(req, 'disabledTypes');
      if (disabled) {
        // Take care to clone so we don't wind up modifying
        // the original schema, the allowed schema is only
        // a shallow clone of the array so far
        type = _.cloneDeep(schema[typeIndex]);
        type.choices = _.filter(type.choices, function(choice) {
          return !_.contains(disabled, choice.value);
        });
        // Make sure the allowed schema refers to the clone,
        // not the original
        schema[typeIndex] = type;
      }
    }
    return schema;
  };

  self.composeSchema = function() {

    // If a type is adminOnly remove the fields relating to permissions editing
    if (self.isAdminOnly()) {
      options.removeFields = (options.removeFields || []).concat([
        'loginRequired',
        '_viewUsers',
        '_viewGroups',
        '_editUsers',
        '_editGroups'
      ]);
    }

    self.schema = self.apos.schemas.compose(self.options);

    // Extend `composeSchema` to flag the use of field names
    // that are forbidden or nonfunctional in all doc types, i.e.
    // properties that will be overwritten by non-schema-driven
    // logic, such as `_id` or `docPermissions`

    const forbiddenFields = [ '_id', 'docPermissions', 'titleSortified', 'highSearchText', 'highSearchWords', 'lowSearchText', 'searchSummary' ];
    _.each(self.schema, function(field) {
      if (_.contains(forbiddenFields, field.name)) {
        throw new Error('Doc type ' + self.name + ': the field name ' + field.name + ' is forbidden');
      }
    });

    self.patchAdminPermissionInSchema();

  };

  // In the schema, `_editUsers` and `_editGroups` are
  // initially locked down to sitewide admins. Now that
  // we've constructed the module completely, take advantage
  // of `getAdminPermissionName` to specify a more nuanced permission,
  // such as the admin permission for this piece type, or for pages

  self.patchAdminPermissionInSchema = function() {
    var fieldNames = [ '_editUsers', '_editGroups' ];
    _.each(fieldNames, function(fieldName) {
      var field = _.find(self.schema, { name: fieldName });
      if (field) {
        field.permission = self.getAdminPermissionName();
      }
    });
  };

  // This method provides the back end of /autocomplete routes.
  // For the implementation of the autocomplete() filter see autocomplete.js.
  //
  // "query" must contain a "field" property which is the schema join field
  // that describes the relationship we're adding items to.
  //
  // "query" must also contain a "term" property, which is a partial
  // string to be autocompleted; otherwise an empty array is delivered
  // to the callback.
  //
  // We don't launder the input here, see the 'autocomplete' route.

  self.autocomplete = function(req, query, callback) {
    var cursor = self.find(req, {}, {}).sort('search');
    if (self.extendAutocompleteCursor) {
      self.extendAutocompleteCursor(cursor);
    }

    cursor.projection(self.getAutocompleteProjection(), query);

    // Try harder not to call autocomplete with something that doesn't
    // result in a search
    if (query.term && query.term.toString && query.term.toString().length) {
      var term = self.apos.launder.string(query.term);
      cursor.autocomplete(term);
    } else {
      return callback(null, []);
    }

    if (!(query.filters && query.filters.limit)) {
      cursor.limit(10);
    }

    // This is the big payoff of blessing join fields: we can apply their
    // filters in API calls like this one, knowing that the field definition
    // the browser sent us is one the developer created
    cursor.queryToFilters(query.field.filters || {});

    // Format it as value & id properties for compatibility with jquery UI autocomplete
    cursor.toArray(function(err, docs) {
      if (err) {
        return callback(err);
      }
      // Put the snippets in id order
      if (query.values) {
        docs = self.apos.utils.orderById(query.values, docs);
      }
      var results = _.map(docs, function(doc) {
        var response = {
          // jquery ui autocomplete demands label and value
          label: self.getAutocompleteTitle(doc, query),
          value: doc._id
        };
        _.defaults(response, _.omit(doc, 'title', '_id'));
        return response;
      });
      return callback(null, results);
    });
  };

  // Adds a listener for the `docSearchIndex` Apostrophe event, pointing to the
  // `searchIndexListener` method.

  self.addSearchIndexListener = function() {
    self.apos.on('docSearchIndex', self.searchIndexListener);
  };

  // Invoked when a `docSearchIndex` event takes place, this method adds
  // additional search texts to the `texts` array (modify it in place by
  // pushing new objects to it). These texts influence search results.
  // The default behavior is quite useful, so you won't often need to
  // override this.
  //
  // Each "text" is an *object* and must have at least `weight` and `text` properties.
  // If `weight` is >= 10, the text will be included in autocomplete searches and
  // given higher priority in full-text searches. Otherwise it will be included
  // only in full-text searches.
  //
  // If `silent` is `true`, the `searchSummary` property will not contain
  // the text.
  //
  // By default this method invokes `schemas.indexFields`, which will push
  // texts for all of the schema fields that support this unless they are
  // explicitly set `searchable: false`.
  //
  // In any case, the text of rich text widgets is always included as
  // lower-priority search text.

  self.searchIndexListener = function(doc, texts) {
    if (doc.type !== self.name) {
      return;
    }
    self.apos.schemas.indexFields(self.schema, doc, texts);
  };

  // Fields required to compute the `_url` property.
  // Used to implement a "projection" for `_url` if
  // requested by the developer
  self.getUrlFields = function() {
    return [ 'type', 'slug', 'tags' ];
  };

  // Override to modify `data` before it is passed to
  // the `chooserModal.html` template
  self.beforeChooserModal = function(req, data) {
  };

  // Most of the time, this is called for you. Any schema field
  // with `sortify: true` will automatically get a migration to
  // ensure that, if the field is named `lastName`, then
  // `lastNameSortified` exists.
  //
  // Adds a migration that takes the given field, such as `lastName`, and
  // creates a parallel `lastNameSortified` field, formatted with
  // `apos.utils.sortify` so that it sorts and compares in a more
  // intuitive, case-insensitive way.
  //
  // After adding such a migration, you can add `sortify: true` to the
  // schema field declaration for `field`, and any calls to
  // the `sort()` cursor filter for `lastName` will automatically
  // use `lastNameSortified`. You can also do that explicitly of course.
  //
  // Note that you want to do both things (add the migration, and
  // add `sortify: true`) because `sortify: true` guarantees that
  // `lastNameSortified` gets updated on all saves of a doc of this type.
  // The migration is a one-time fix for existing data.

  self.addSortifyMigration = function(field) {
    if (!self.name) {
      return;
    }
    return self.apos.migrations.addSortify(self.__meta.name, { type: self.name }, field);
  };

};
<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2020 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

/*
* When adding modules to the namespace, ensure that they are added in alphabetical order according to module name.
*/

// MODULES //

var setReadOnly = require( '@stdlib/utils/define-read-only-property' );

// MAIN //

/**
* Top-level namespace.
*
* @namespace ns
*/
var ns = {};

/**
* @name binary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/binary}
*/
setReadOnly( ns, 'binary', require( '@stdlib/strided/base/binary' ) );

/**
* @name binaryAddonDispatch
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/binary-addon-dispatch}
*/
setReadOnly( ns, 'binaryAddonDispatch', require( '@stdlib/strided/base/binary-addon-dispatch' ) );

/**
* @name dmap
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dmap}
*/
setReadOnly( ns, 'dmap', require( '@stdlib/strided/base/dmap' ) );

/**
* @name dmap2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dmap2}
*/
setReadOnly( ns, 'dmap2', require( '@stdlib/strided/base/dmap2' ) );

/**
* @name dmskmap
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dmskmap}
*/
setReadOnly( ns, 'dmskmap', require( '@stdlib/strided/base/dmskmap' ) );

/**
* @name dmskmap2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dmskmap2}
*/
setReadOnly( ns, 'dmskmap2', require( '@stdlib/strided/base/dmskmap2' ) );

/**
* @name dtypeEnum2Str
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dtype-enum2str}
*/
setReadOnly( ns, 'dtypeEnum2Str', require( '@stdlib/strided/base/dtype-enum2str' ) );

/**
* @name dtypeResolveEnum
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dtype-resolve-enum}
*/
setReadOnly( ns, 'dtypeResolveEnum', require( '@stdlib/strided/base/dtype-resolve-enum' ) );

/**
* @name dtypeStr2Enum
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dtype-str2enum}
*/
setReadOnly( ns, 'dtypeStr2Enum', require( '@stdlib/strided/base/dtype-str2enum' ) );

/**
* @name mapBy
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/map-by}
*/
setReadOnly( ns, 'mapBy', require( '@stdlib/strided/base/map-by' ) );

/**
* @name mapBy2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/map-by2}
*/
setReadOnly( ns, 'mapBy2', require( '@stdlib/strided/base/map-by2' ) );

/**
* @name metaDataProps
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/meta-data-props}
*/
setReadOnly( ns, 'metaDataProps', require( '@stdlib/strided/base/meta-data-props' ) );

/**
* @name minViewBufferIndex
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/min-view-buffer-index}
*/
setReadOnly( ns, 'minViewBufferIndex', require( '@stdlib/strided/base/min-view-buffer-index' ) );

/**
* @name mskunary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/mskunary}
*/
setReadOnly( ns, 'mskunary', require( '@stdlib/strided/base/mskunary' ) );

/**
* @name nullary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/nullary}
*/
setReadOnly( ns, 'nullary', require( '@stdlib/strided/base/nullary' ) );

/**
* @name offsetView
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/offset-view}
*/
setReadOnly( ns, 'offsetView', require( '@stdlib/strided/base/offset-view' ) );

/**
* @name quaternary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/quaternary}
*/
setReadOnly( ns, 'quaternary', require( '@stdlib/strided/base/quaternary' ) );

/**
* @name quinary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/quinary}
*/
setReadOnly( ns, 'quinary', require( '@stdlib/strided/base/quinary' ) );

/**
* @name reinterpretComplex64
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/reinterpret-complex64}
*/
setReadOnly( ns, 'reinterpretComplex64', require( '@stdlib/strided/base/reinterpret-complex64' ) );

/**
* @name reinterpretComplex128
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/reinterpret-complex128}
*/
setReadOnly( ns, 'reinterpretComplex128', require( '@stdlib/strided/base/reinterpret-complex128' ) );

/**
* @name smap
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/smap}
*/
setReadOnly( ns, 'smap', require( '@stdlib/strided/base/smap' ) );

/**
* @name smap2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/smap2}
*/
setReadOnly( ns, 'smap2', require( '@stdlib/strided/base/smap2' ) );

/**
* @name smskmap
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/smskmap}
*/
setReadOnly( ns, 'smskmap', require( '@stdlib/strided/base/smskmap' ) );

/**
* @name smskmap2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/smskmap2}
*/
setReadOnly( ns, 'smskmap2', require( '@stdlib/strided/base/smskmap2' ) );

/**
* @name ternary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/ternary}
*/
setReadOnly( ns, 'ternary', require( '@stdlib/strided/base/ternary' ) );

/**
* @name unary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/unary}
*/
setReadOnly( ns, 'unary', require( '@stdlib/strided/base/unary' ) );

/**
* @name unaryAddonDispatch
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/unary-addon-dispatch}
*/
setReadOnly( ns, 'unaryAddonDispatch', require( '@stdlib/strided/base/unary-addon-dispatch' ) );

// EXPORTS //

module.exports = ns;
<commit_msg>Update namespace<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2020 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

/*
* When adding modules to the namespace, ensure that they are added in alphabetical order according to module name.
*/

// MODULES //

var setReadOnly = require( '@stdlib/utils/define-read-only-property' );

// MAIN //

/**
* Top-level namespace.
*
* @namespace ns
*/
var ns = {};

/**
* @name binary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/binary}
*/
setReadOnly( ns, 'binary', require( '@stdlib/strided/base/binary' ) );

/**
* @name binaryAddonDispatch
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/binary-addon-dispatch}
*/
setReadOnly( ns, 'binaryAddonDispatch', require( '@stdlib/strided/base/binary-addon-dispatch' ) );

/**
* @name dmap
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dmap}
*/
setReadOnly( ns, 'dmap', require( '@stdlib/strided/base/dmap' ) );

/**
* @name dmap2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dmap2}
*/
setReadOnly( ns, 'dmap2', require( '@stdlib/strided/base/dmap2' ) );

/**
* @name dmskmap
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dmskmap}
*/
setReadOnly( ns, 'dmskmap', require( '@stdlib/strided/base/dmskmap' ) );

/**
* @name dmskmap2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dmskmap2}
*/
setReadOnly( ns, 'dmskmap2', require( '@stdlib/strided/base/dmskmap2' ) );

/**
* @name dtypeEnum2Str
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dtype-enum2str}
*/
setReadOnly( ns, 'dtypeEnum2Str', require( '@stdlib/strided/base/dtype-enum2str' ) );

/**
* @name dtypeResolveEnum
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dtype-resolve-enum}
*/
setReadOnly( ns, 'dtypeResolveEnum', require( '@stdlib/strided/base/dtype-resolve-enum' ) );

/**
* @name dtypeStr2Enum
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/dtype-str2enum}
*/
setReadOnly( ns, 'dtypeStr2Enum', require( '@stdlib/strided/base/dtype-str2enum' ) );

/**
* @name mapBy
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/map-by}
*/
setReadOnly( ns, 'mapBy', require( '@stdlib/strided/base/map-by' ) );

/**
* @name mapBy2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/map-by2}
*/
setReadOnly( ns, 'mapBy2', require( '@stdlib/strided/base/map-by2' ) );

/**
* @name maxViewBufferIndex
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/max-view-buffer-index}
*/
setReadOnly( ns, 'maxViewBufferIndex', require( '@stdlib/strided/base/max-view-buffer-index' ) );

/**
* @name metaDataProps
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/meta-data-props}
*/
setReadOnly( ns, 'metaDataProps', require( '@stdlib/strided/base/meta-data-props' ) );

/**
* @name minViewBufferIndex
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/min-view-buffer-index}
*/
setReadOnly( ns, 'minViewBufferIndex', require( '@stdlib/strided/base/min-view-buffer-index' ) );

/**
* @name mskunary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/mskunary}
*/
setReadOnly( ns, 'mskunary', require( '@stdlib/strided/base/mskunary' ) );

/**
* @name nullary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/nullary}
*/
setReadOnly( ns, 'nullary', require( '@stdlib/strided/base/nullary' ) );

/**
* @name offsetView
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/offset-view}
*/
setReadOnly( ns, 'offsetView', require( '@stdlib/strided/base/offset-view' ) );

/**
* @name quaternary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/quaternary}
*/
setReadOnly( ns, 'quaternary', require( '@stdlib/strided/base/quaternary' ) );

/**
* @name quinary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/quinary}
*/
setReadOnly( ns, 'quinary', require( '@stdlib/strided/base/quinary' ) );

/**
* @name reinterpretComplex64
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/reinterpret-complex64}
*/
setReadOnly( ns, 'reinterpretComplex64', require( '@stdlib/strided/base/reinterpret-complex64' ) );

/**
* @name reinterpretComplex128
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/reinterpret-complex128}
*/
setReadOnly( ns, 'reinterpretComplex128', require( '@stdlib/strided/base/reinterpret-complex128' ) );

/**
* @name smap
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/smap}
*/
setReadOnly( ns, 'smap', require( '@stdlib/strided/base/smap' ) );

/**
* @name smap2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/smap2}
*/
setReadOnly( ns, 'smap2', require( '@stdlib/strided/base/smap2' ) );

/**
* @name smskmap
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/smskmap}
*/
setReadOnly( ns, 'smskmap', require( '@stdlib/strided/base/smskmap' ) );

/**
* @name smskmap2
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/smskmap2}
*/
setReadOnly( ns, 'smskmap2', require( '@stdlib/strided/base/smskmap2' ) );

/**
* @name ternary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/ternary}
*/
setReadOnly( ns, 'ternary', require( '@stdlib/strided/base/ternary' ) );

/**
* @name unary
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/unary}
*/
setReadOnly( ns, 'unary', require( '@stdlib/strided/base/unary' ) );

/**
* @name unaryAddonDispatch
* @memberof ns
* @readonly
* @type {Function}
* @see {@link module:@stdlib/strided/base/unary-addon-dispatch}
*/
setReadOnly( ns, 'unaryAddonDispatch', require( '@stdlib/strided/base/unary-addon-dispatch' ) );

// EXPORTS //

module.exports = ns;
<|endoftext|>


<commit_before>'use strict';

/**
 * uischema.org processor
 *
 * @memberof HashBrown.Server.Entity
 */
class UISchemaProcessor extends HashBrown.Entity.Processor.ProcessorBase {
    /**
     * Performs a value check
     *
     * @param {*} value
     * @param {String} schemaId
     * @param {Object} config
     * @param {String} locale
     *
     * @return {*} Value
     */
    async check(value, schemaId, config, locale) {
        if(value === null || value === undefined || value === '') { return null; }

        if(Array.isArray(value)) {
            schemaId = 'array';
        }

        if(!schemaId) { return value; }

        let schema = await HashBrown.Entity.Resource.FieldSchema.get(this.context, schemaId, { withParentFields: true });

        if(!schema) { return value; }

        // Ensure config
        if(!config) {
            config = {};
        }

        // Merge schema config
        if(schema.config) {
            for(let k in schema.config) {
                if(config[k]) { continue; }

                config[k] = schema.config[k];
            }
        }

        // This schema is a struct
        if(config.struct) {
            schemaId = 'struct';
        }

        let parsed = {};

        switch(schemaId) {
            default:
                parsed = value;
                break;

            case 'array':
                parsed['@type'] = 'ItemList';
                parsed['numberOfItems'] = value.length;
                parsed['itemListElement'] = [];

                for(let i in value) {
                    parsed['itemListElement'].push({
                        '@type': 'ItemListElement',
                        'position': i,
                        'item': await this.check(value[i].value, value[i].schemaId, null, locale)
                    });
                }
                break;

            case 'struct':
                parsed['@type'] = schema.id || 'StructuredValue';

                for(let k in value) {
                    if(!value[k] || !config.struct || !config.struct[k] || !config.struct[k].schemaId) { continue; }

                    parsed[k] = await this.check(value[k], config.struct[k].schemaId, config.struct[k].config, locale);
                }
                break;

            case 'mediaReference':
                let media = await HashBrown.Entity.Resource.Media.get(this.context, value);

                if(!media) { return null; }

                if(media.isImage()) {
                    parsed['@type'] = 'ImageObject';
                } else if(media.isAudio()) {
                    parsed['@type'] = 'AudioObject';
                } else if(media.isVideo()) {
                    parsed['@type'] = 'VideoObject';
                } else if(media.isDocument()) {
                    parsed['@type'] = 'DataDownload';
                } else {
                    parsed['@type'] = 'MediaObject';
                }

                parsed['author'] = media.author;

                if(parsed['author']) {
                    parsed['author']['@type'] = 'Person';
                }

                parsed['copyrightHolder'] = media.copyrightHolder;

                if(parsed['copyrightHolder']) {
                    parsed['copyrightHolder']['@type'] = 'Organization';
                }

                parsed['copyrightYear'] = media.copyrightYear;

                parsed['name'] = media.filename;
                parsed['contentUrl'] = await media.getContentUrl(true);
                parsed['thumbnailUrl'] = await media.getThumbnailUrl(true);
                break;

            case 'contentReference':
                let content = await HashBrown.Entity.Resource.Content.get(this.context, value);

                content = await this.process(content, locale);

                for(let key in content) {
                    parsed[key] = content[key];
                }
                break;
        }

        return parsed;
    }

    /**
     * Compiles content as uischema.org JSON
     *
     * @param {Content} content
     * @param {String} locale
     * @param {Array} filter
     *
     * @returns {Promise} Result
     */
    async process(content, locale = 'en', filter = []) {
        checkParam(content, 'content', HashBrown.Entity.Resource.Content, true);
        checkParam(locale, 'locale', String, true);
        checkParam(filter, 'filter', Array, true);

        let schema = await HashBrown.Entity.Resource.ContentSchema.get(this.context, content.schemaId);
        let properties = content.getLocalizedProperties(locale);
        let meta = content.getMeta();

        if(!properties) {
            throw new Error(`No properties for content "${content.getName()}" with locale "${locale}"`);
        }

        let createdBy = await content.getCreatedBy();
        let updatedBy = await content.getUpdatedBy();

        // We'll have to a allow unknown authors, as they could disappear between backups
        if(!createdBy) {
            createdBy = HashBrown.Entity.User.new({
                fullName: 'Unknown',
                username: 'unknown'
            });
        }

        if(!updatedBy) {
            updatedBy = HashBrown.Entity.User.new({
                fullName: 'Unknown',
                username: 'unknown'
            });
        }

        // Combine all data into one
        let data = {};

        data['@type'] = schema.id;
        data['@parentId'] = content.parentId;

        // TODO: Include images here?
        data.creator = {
            '@type': 'Person',
            name: createdBy.getName(),
            email: createdBy.email
        };

        data.author = {
            '@type': 'Person',
            name: updatedBy.getName(),
            email: updatedBy.email
        };

        data.inLanguage = locale;

        data.dateCreated = content.createdOn;
        data.dateModified = content.updatedOn;
        data.datePublished = content.createdOn;

        for(let key in schema.config) {
            if(filter.length > 0 && filter.indexOf(key) < 0) { continue; }

            data[key] = await this.check(properties[key], schema.config[key].schemaId, schema.config[key].config, locale);
        }

        return data;
    }
}

module.exports = UISchemaProcessor;
<commit_msg>Added identifier to uischema<commit_after>'use strict';

/**
 * uischema.org processor
 *
 * @memberof HashBrown.Server.Entity
 */
class UISchemaProcessor extends HashBrown.Entity.Processor.ProcessorBase {
    /**
     * Performs a value check
     *
     * @param {*} value
     * @param {String} schemaId
     * @param {Object} config
     * @param {String} locale
     *
     * @return {*} Value
     */
    async check(value, schemaId, config, locale) {
        if(value === null || value === undefined || value === '') { return null; }

        if(Array.isArray(value)) {
            schemaId = 'array';
        }

        if(!schemaId) { return value; }

        let schema = await HashBrown.Entity.Resource.FieldSchema.get(this.context, schemaId, { withParentFields: true });

        if(!schema) { return value; }

        // Ensure config
        if(!config) {
            config = {};
        }

        // Merge schema config
        if(schema.config) {
            for(let k in schema.config) {
                if(config[k]) { continue; }

                config[k] = schema.config[k];
            }
        }

        // This schema is a struct
        if(config.struct) {
            schemaId = 'struct';
        }

        switch(schemaId) {
            default:
                parsed = value;
                break;

            case 'array':
                parsed['@type'] = 'ItemList';
                parsed['numberOfItems'] = value.length;
                parsed['itemListElement'] = [];

                for(let i in value) {
                    parsed['itemListElement'].push({
                        '@type': 'ItemListElement',
                        'position': i,
                        'item': await this.check(value[i].value, value[i].schemaId, null, locale)
                    });
                }
                break;

            case 'struct':
                parsed['@type'] = schema.id || 'StructuredValue';

                for(let k in value) {
                    if(!value[k] || !config.struct || !config.struct[k] || !config.struct[k].schemaId) { continue; }

                    parsed[k] = await this.check(value[k], config.struct[k].schemaId, config.struct[k].config, locale);
                }
                break;

            case 'mediaReference':
                let media = await HashBrown.Entity.Resource.Media.get(this.context, value);

                if(!media) { return null; }

                if(media.isImage()) {
                    parsed['@type'] = 'ImageObject';
                } else if(media.isAudio()) {
                    parsed['@type'] = 'AudioObject';
                } else if(media.isVideo()) {
                    parsed['@type'] = 'VideoObject';
                } else if(media.isDocument()) {
                    parsed['@type'] = 'DataDownload';
                } else {
                    parsed['@type'] = 'MediaObject';
                }

                parsed['author'] = media.author;

                if(parsed['author']) {
                    parsed['author']['@type'] = 'Person';
                }

                parsed['copyrightHolder'] = media.copyrightHolder;

                if(parsed['copyrightHolder']) {
                    parsed['copyrightHolder']['@type'] = 'Organization';
                }

                parsed['copyrightYear'] = media.copyrightYear;

                parsed['name'] = media.filename;
                parsed['contentUrl'] = await media.getContentUrl(true);
                parsed['thumbnailUrl'] = await media.getThumbnailUrl(true);
                break;

            case 'contentReference':
                let content = await HashBrown.Entity.Resource.Content.get(this.context, value);

                content = await this.process(content, locale);

                for(let key in content) {
                    parsed[key] = content[key];
                }
                break;
        }

        return parsed;
    }

    /**
     * Compiles content as uischema.org JSON
     *
     * @param {Content} content
     * @param {String} locale
     * @param {Array} filter
     *
     * @returns {Promise} Result
     */
    async process(content, locale = 'en', filter = []) {
        checkParam(content, 'content', HashBrown.Entity.Resource.Content, true);
        checkParam(locale, 'locale', String, true);
        checkParam(filter, 'filter', Array, true);

        let schema = await HashBrown.Entity.Resource.ContentSchema.get(this.context, content.schemaId);
        let properties = content.getLocalizedProperties(locale);
        let meta = content.getMeta();

        if(!properties) {
            throw new Error(`No properties for content "${content.getName()}" with locale "${locale}"`);
        }

        let createdBy = await content.getCreatedBy();
        let updatedBy = await content.getUpdatedBy();

        // We'll have to a allow unknown authors, as they could disappear between backups
        if(!createdBy) {
            createdBy = HashBrown.Entity.User.new({
                fullName: 'Unknown',
                username: 'unknown'
            });
        }

        if(!updatedBy) {
            updatedBy = HashBrown.Entity.User.new({
                fullName: 'Unknown',
                username: 'unknown'
            });
        }

        // Combine all data into one
        let data = {};

        data['@type'] = schema.id;
        data['@parentId'] = content.parentId;
        data['identifier'] = content.id;

        // TODO: Include images here?
        data.creator = {
            '@type': 'Person',
            name: createdBy.getName(),
            email: createdBy.email
        };

        data.author = {
            '@type': 'Person',
            name: updatedBy.getName(),
            email: updatedBy.email
        };

        data.inLanguage = locale;

        data.dateCreated = content.createdOn;
        data.dateModified = content.updatedOn;
        data.datePublished = content.createdOn;

        for(let key in schema.config) {
            if(filter.length > 0 && filter.indexOf(key) < 0) { continue; }

            data[key] = await this.check(properties[key], schema.config[key].schemaId, schema.config[key].config, locale);
        }

        return data;
    }
}

module.exports = UISchemaProcessor;
<|endoftext|>


<commit_before>(function () {
    'use strict';
    angular.module('Tombola.Games.Bingo90.Core')
        .service('BingoTicket', [ function() {
            var me = this;

            me.pushArray = function (card) {
                console.log(card);

                };

            //ticketLength = bingoTicket.length,
            //tickets = [];

            //$scope.bingoStrip = [];

            //var createBingoTicketGrid = function () {
            //    me.ticketGrid = [
            //        [null, null,null,null,null,null,null,null,null],
            //        [null, null,null,null,null,null,null,null,null],
            //        [null, null,null,null,null,null,null,null,null]
            //    ];
            //};

            //var generateTheTicket = function (ticket) {
            //    var startOfString = 0;
            //    var endOfString = 30;
            //    for(startOfString; startOfString < ticket.length; startOfString += 30){
            //        tickets.push(ticket.slice(startOfString, endOfString));
            //        endOfString += 30;
            //    }
            //};

        }]);
})();<commit_msg>Each ticket splits up and the balls also split up<commit_after>(function () {
    'use strict';
    angular.module('Tombola.Games.Bingo90.Core')
        .service('BingoTicket', [ function() {
            var me = this;
            var tickets = [],
                balls = [];

            me.pushArray = function (card) {
                var cardss = "054963758028345266770611596982054963758028345266770611596982";
                generateTheTicket(cardss);
            };

            var createBingoTicketGrid = function () {
                me.ticketGrid = [
                    [null, null,null,null,null,null,null,null,null],
                    [null, null,null,null,null,null,null,null,null],
                    [null, null,null,null,null,null,null,null,null]
                ];
            };

            var generateTheTicket = function (ticket) {
                var startOfString = 0;
                var endOfString = 30;
                for(startOfString; startOfString < ticket.length; startOfString += 30){
                    tickets.push(ticket.slice(startOfString, endOfString));
                    endOfString += 30;
                }
                splitUpTheTicket(tickets[0]);
                console.log(tickets);
            };

            var splitUpTheTicket = function (ticket) {
                var startOfString = 0;
                var endOfString = 2;
                for(startOfString; startOfString < ticket.length; startOfString += 2){
                    balls.push(ticket.slice(startOfString, endOfString));
                    endOfString += 2;
                }
                console.log(balls);
            };

        }]);
})();<|endoftext|>


<commit_before>/*****************************************************************************/
/* PayrunNew: Event Handlers */
/*****************************************************************************/

let ReportUtils = {}

ReportUtils.getPayTypeHeaders2 = function(employeePayments) {
    let payTypeHeaders = ['EMP ID', 'Employee']

    let payGrade =  null
    let firstUserId = employeePayments[0].employeeId
    let firstUser = Meteor.users.findOne(firstUserId)
    if(firstUser) {
        let payGradeId = firstUser.employeeProfile.employment.paygrade
        if(payGradeId) {
            payGrade = PayGrades.findOne(payGradeId)
        }
    }

     let numPaytypesBeforeSuppl = 0
    // Supplementary payments are payments like netpay,
    // pension employee contrib and pension employer contrib

    let numberOfPayments = 0
    if(payGrade) {
        if(payGrade.payTypePositionIds && payGrade.payTypePositionIds.length > 0) {
            numberOfPayments = payGrade.payTypePositionIds.length
        } else {
            numberOfPayments = employeePayments[0].payment.length
        }
        numPaytypesBeforeSuppl = numberOfPayments
    } else {
        numberOfPayments = employeePayments[0].payment.length
        numPaytypesBeforeSuppl = payTypeHeaders.length - 2  // EMP ID, Employee
    }

    // 5 === 1 for employee column PLUS 2 for deductions and net pay
    // PLUS 3 for pension employee and pension employer
    let headerColumnSlots = _.range(numberOfPayments - 5).map(x => {
        return {}
    })

    payTypeHeaders.push(...headerColumnSlots)
    let supplementaryPayTypeHeaders = [
    {
        id: 'totalDeduction',
        code: 'totalDeduction',
        description: 'Total Deduction'
    },
    {
        id: 'netPay',
        code: 'netPay',
        description: 'Net Pay'
    }]
    payTypeHeaders.push(...supplementaryPayTypeHeaders)
    //--

    employeePayments.forEach(anEmployeeData => {
        anEmployeeData.payment.forEach((anEmployeePayType, empPayTypeIndex) => {
            if(anEmployeePayType.id) {
                let doesPayTypeHeaderExist = _.find(payTypeHeaders, function(aPayType) {
                    return aPayType.id && (aPayType.id === anEmployeePayType.id && aPayType.code === anEmployeePayType.code)
                })
                if(!doesPayTypeHeaderExist) {
                    if(payGrade) {
                        let payGradePaytypeDetails = _.find(payGrade.payTypePositionIds, function(payGradePaytype) {
                            return payGradePaytype.paytype === anEmployeePayType.id
                        })
                        if(payGradePaytypeDetails) {// Adding '1' cos of the first 'EMP ID' and 'Employee' columns
                            if(payGradePaytypeDetails.hasOwnProperty("paySlipPositionId")) {
                                payTypeHeaders[payGradePaytypeDetails.paySlipPositionId + 2] = {
                                    id: anEmployeePayType.id,
                                    code: anEmployeePayType.code,
                                    description: anEmployeePayType.description
                                }
                            }
                        } else {
                            payTypeHeaders.splice(numPaytypesBeforeSuppl - 3, 0, {
                                id: anEmployeePayType.id,
                                code: anEmployeePayType.code,
                                description: anEmployeePayType.description
                            })
                        }
                    }
                }
            }
        })
    })

    return {payTypeHeaders}
}

// ReportUtils.getPayTypeHeaders = function(employeePayments) {
//     let payTypeHeaders = ['Employee']

//     employeePayments.forEach(anEmployeeData => {
//         anEmployeeData.payment.forEach(anEmployeePayType => {
//             if(anEmployeePayType.id) {
//                 let doesPayTypeHeaderExist = _.find(payTypeHeaders, function(aPayType) {
//                     return aPayType.id && (aPayType.id === anEmployeePayType.id && aPayType.code === anEmployeePayType.code)
//                 })
//                 if(!doesPayTypeHeaderExist) {
//                     payTypeHeaders.push({
//                         id: anEmployeePayType.id,
//                         code: anEmployeePayType.code,
//                         description: anEmployeePayType.description
//                     })
//                 }
//             }
//         })
//     })
//     payTypeHeaders.push({
//         id: 'totalDeduction',
//         code: 'totalDeduction',
//         description: 'Total Deduction'
//     })
//     payTypeHeaders.push({
//         id: 'netPay',
//         code: 'netPay',
//         description: 'Net Pay'
//     })
//     return {payTypeHeaders}
// }

ReportUtils.getPayTypeValues = function(employeePayments, payTypeHeaders) {
    let payTypeValues = []

    employeePayments.forEach(anEmployeeData => {
        let aRowOfPayTypeValues = []

        payTypeHeaders.forEach(aPaytypeHeader => {
            if(aPaytypeHeader === 'EMP ID') {
                let employee = Meteor.users.findOne({_id: anEmployeeData.employeeId});
                aRowOfPayTypeValues.push(employee.employeeProfile.employeeId)
                return
            }
            if(aPaytypeHeader === 'Employee') {
                let employee = Meteor.users.findOne({_id: anEmployeeData.employeeId});
                aRowOfPayTypeValues.push(employee.profile.fullName)
                return
            }
            //--
            let payDetails = _.find(anEmployeeData.payment, function(aPayType) {
                return aPayType.id && (aPaytypeHeader.id === aPayType.id && aPaytypeHeader.code === aPayType.code)
            })
            if(payDetails) {
                let payAmount = payDetails.amountLC
                if(payDetails.amountLC != payDetails.amountPC) {
                    payAmount = payDetails.amountPC
                }
                if(payDetails.type === 'Deduction') {
                    if(payAmount > 0) {
                        payAmount *= -1
                    }
                }
                aRowOfPayTypeValues.push(payAmount)
            } else if(aPaytypeHeader.id === 'netPay') {

                let netPay = _.find(anEmployeeData.payment, function(aPayType) {
                    return (aPayType.code === 'NMP')
                })
                if(netPay) {
                    let payAmount = netPay.amountLC
                    if(netPay.amountLC != netPay.amountPC) {
                        payAmount = netPay.amountPC
                    }
                    aRowOfPayTypeValues.push(payAmount)
                }
            } else if(aPaytypeHeader.id === 'totalDeduction') {
                let totalDeduction = _.find(anEmployeeData.payment, function(aPayType) {
                    return (aPayType.code === 'TDEDUCT')
                })
                if(totalDeduction) {
                    let payAmount = totalDeduction.amountLC
                    if(totalDeduction.amountLC != totalDeduction.amountPC) {
                        payAmount = totalDeduction.amountPC
                    }
                    aRowOfPayTypeValues.push(payAmount)
                }
            } else {
                aRowOfPayTypeValues.push("---")
            }
        })
        payTypeValues.push(aRowOfPayTypeValues)
    })
    return payTypeValues
}

Template.PayrunNew.events({
    'click #startProcessing': (e,tmpl) => {
        let payGrades = function(){
            let selected = [];
            $("input:checkbox[name=paygrades]:checked").each(function () {
                selected.push($(this).attr("id"));
            });
            return selected;
        };
        let annualPay = function(){
            let selected = [];
            $("input:checkbox[name=selectAnnual]:checked").each(function () {
                selected.push($(this).attr("id"));
            });
            console.log(selected);
            return selected;
        };
        var view = Blaze.render(Template.Loading, document.getElementById('spinner'));
        const params = {
            employees: Core.returnSelection($('[name="employee"]')),
            paygrades: payGrades(),
            period: {
                month: $('[name="paymentPeriod.month"]').val(),
                year: $('[name="paymentPeriod.year"]').val(),

            },
            type: $('input[name=runtype]:checked').val(),
            annuals: annualPay()
        };
        //check if any valid selection is made
        if (params.employees.length > 0 || params.paygrades.length > 0){
            Meteor.call("payrun/process",  params, Session.get('context'), (err, res) => {
                if(res){
                    // console.log(`Payrun results: ${JSON.stringify(res)}`);
                    Session.set('currentPayrunPeriod', res.period);

                    //set result as reactive dict payResult
                    tmpl.dict.set("payResult", res);
                } else{
                    console.log(err);
                }
                Blaze.remove(view);
            })
        } else {
            Blaze.remove(view);
            swal("notice","A valid selection must be made", "error");
        }

    },
    'change [name="annualPay"]': (e, tmpl) => {
        tmpl.includePay.set($(e.target).is(':checked'));

    },
    'change [name="employee"]': (e, tmpl) => {
        let selected = Core.returnSelection($(e.target));
        selected.length > 0 ? tmpl.showPayGrade.set(false):tmpl.showPayGrade.set(true);
        //add employee paygrades
        let employeeGrade = selected.map(x => {
            return Meteor.users.findOne({_id: x, 'employee': true}).employeeProfile.employment.paygrade;
        });
        tmpl.grades.set(employeeGrade);
    },
    'change [name="paygrades"]': (e, tmpl) => {
        let selected = [];
        $("input:checkbox[name=paygrades]:checked").each(function () {
            selected.push($(this).attr("id"));
        });
        tmpl.grades.set(selected);
    },
    'hover .table tbody tr': (e,tmpl) => {
        console.log('hover called');
    },
    selectedMonth: function (val) {
        if(Template.instance().selectedMonth.get()) {
            return Template.instance().selectedMonth.get() === val ? selected="selected" : '';
        }
    },
    selectedYear: function (val) {
        if(Template.instance().selectedYear.get()) {
            return Template.instance().selectedYear.get() === val ? selected="selected" : '';
        }
    },
    'click #export-to-csv': (e,tmpl) => {
        const payResult = Template.instance().dict.get('payResult');
        if (payResult) {
            // console.log(`payResult: `, payResult)

            let payTypeHeaders = ReportUtils.getPayTypeHeaders2(payResult.payObj.payrun)

            let formattedHeader = payTypeHeaders.payTypeHeaders.map(aHeader => {
                return aHeader.description || aHeader
            })
            //--
            let reportData = ReportUtils.getPayTypeValues(payResult.payObj.payrun, payTypeHeaders.payTypeHeaders)

            BulkpayExplorer.exportAllData({fields: formattedHeader, data: reportData},
                `Payrun results export`);
        } else {
            swal('Error', 'No payrun results', 'error')
        }
    }
});

/*****************************************************************************/
/* PayrunNew: Helpers */
/*****************************************************************************/
Template.PayrunNew.helpers({
    'month': function(){
        return Core.months()
    },
    'year': function(){
        return Core.years();
    },
    'paygrades': () => {
       return PayGrades.find();
    },
    'employees': () => {
        return Meteor.users.find({"employee": true});
    },
    "showPayGrade": () => {
        return Template.instance().showPayGrade.get();
    },
    "showAnnualPay": () => {
        return Template.instance().includePay.get();
    },
    'annualPay': () => {
        return PayTypes.find().fetch();
    },
    'checkInitial': (index) => {
        return index === 0 ? 'checked': null;
    },
    'nopayresult': () =>{
        const payresult = Template.instance().dict.get('payResult');
        if (!payresult) return true;
        return false;
    },
    'employeeResult': () => {
        const payResult = Template.instance().dict.get('payResult');
        if (payResult) {
            console.log(`pay result`, payResult.payObj.result)
            return payResult.payObj.result;
        }
    },
    'processingError': () => {
        const payresult = Template.instance().dict.get('payResult');
        return payresult.payObj.error.length;
    },
    selectedMonth: function (val) {
        if(Template.instance().selectedMonth.get()) {
            return Template.instance().selectedMonth.get() === val ? selected="selected" : '';
        }
    },
    selectedYear: function (val) {
        if(Template.instance().selectedYear.get()) {
            return Template.instance().selectedYear.get() === val ? selected="selected" : '';
        }
    }
});

/*****************************************************************************/
/* Payments: Lifecycle Hooks */
/*****************************************************************************/
Template.PayrunNew.onCreated(function () {
    let self = this;
    self.subscribe("paygrades", Session.get('context'));
    self.subscribe("activeEmployees", Session.get('context'));
    self.dict = new ReactiveDict();
    self.showPayGrade = new ReactiveVar(true);
    self.grades = new ReactiveVar([]);
    self.includePay = new ReactiveVar(false);
    // if annual payment included, subscribe to all annual pay.

    //--
    self.selectedMonth = new ReactiveVar();
    self.selectedYear = new ReactiveVar();
    //--
    let theMoment = moment();
    self.selectedMonth.set(theMoment.format('MM'))
    self.selectedYear.set(theMoment.format('YYYY'))
    //--

    self.autorun(function(){
        let includeType = self.includePay.get();
        let selectedGrade = self.grades.get();
        if (includeType)
            self.subscribe("AdditionalPayTypes", selectedGrade, Session.get('context'));
    })
});

Template.PayrunNew.onRendered(function () {
     $('select.dropdown').dropdown();
    // //
    // let selected = [];
    // $("input:checkbox[name=paygrades]:checked").each(function () {
    //     selected.push($(this).attr("id"));
    // });
    // Template.instance().grades.set(selected);
    // Show aciton upon row hover
});

Template.PayrunNew.onDestroyed(function () {
});
<commit_msg>Now removing column for payroll export to csv if no employee pay exists for it<commit_after>/*****************************************************************************/
/* PayrunNew: Event Handlers */
/*****************************************************************************/

let ReportUtils = {}

ReportUtils.getPayTypeHeaders2 = function(employeePayments) {
    let payTypeHeaders = ['EMP ID', 'Employee']

    let payGrade =  null
    let firstUserId = employeePayments[0].employeeId
    let firstUser = Meteor.users.findOne(firstUserId)
    if(firstUser) {
        let payGradeId = firstUser.employeeProfile.employment.paygrade
        if(payGradeId) {
            payGrade = PayGrades.findOne(payGradeId)
        }
    }

     let numPaytypesBeforeSuppl = 0
    // Supplementary payments are payments like netpay,
    // pension employee contrib and pension employer contrib

    let numberOfPayments = 0
    if(payGrade) {
        if(payGrade.payTypePositionIds && payGrade.payTypePositionIds.length > 0) {
            numberOfPayments = payGrade.payTypePositionIds.length
        } else {
            numberOfPayments = employeePayments[0].payment.length
        }
        numPaytypesBeforeSuppl = numberOfPayments
    } else {
        numberOfPayments = employeePayments[0].payment.length
        numPaytypesBeforeSuppl = payTypeHeaders.length - 2  // EMP ID, Employee
    }

    // 5 === 1 for employee column PLUS 2 for deductions and net pay
    // PLUS 3 for pension employee and pension employer
    let headerColumnSlots = _.range(numberOfPayments - 5).map(x => {
        return {}
    })

    payTypeHeaders.push(...headerColumnSlots)
    let supplementaryPayTypeHeaders = [
    {
        id: 'totalDeduction',
        code: 'totalDeduction',
        description: 'Total Deduction'
    },
    {
        id: 'netPay',
        code: 'netPay',
        description: 'Net Pay'
    }]
    payTypeHeaders.push(...supplementaryPayTypeHeaders)
    //--

    employeePayments.forEach(anEmployeeData => {
        anEmployeeData.payment.forEach((anEmployeePayType, empPayTypeIndex) => {
            if(anEmployeePayType.id) {
                let doesPayTypeHeaderExist = _.find(payTypeHeaders, function(aPayType) {
                    return aPayType.id && (aPayType.id === anEmployeePayType.id && aPayType.code === anEmployeePayType.code)
                })
                if(!doesPayTypeHeaderExist) {
                    if(payGrade) {
                        let payGradePaytypeDetails = _.find(payGrade.payTypePositionIds, function(payGradePaytype) {
                            return payGradePaytype.paytype === anEmployeePayType.id
                        })
                        if(payGradePaytypeDetails) {// Adding '1' cos of the first 'EMP ID' and 'Employee' columns
                            if(payGradePaytypeDetails.hasOwnProperty("paySlipPositionId")) {
                                payTypeHeaders[payGradePaytypeDetails.paySlipPositionId + 2] = {
                                    id: anEmployeePayType.id,
                                    code: anEmployeePayType.code,
                                    description: anEmployeePayType.description
                                }
                            }
                        } else {
                            payTypeHeaders.splice(numPaytypesBeforeSuppl - 3, 0, {
                                id: anEmployeePayType.id,
                                code: anEmployeePayType.code,
                                description: anEmployeePayType.description
                            })
                        }
                    }
                }
            }
        })
    })
    payTypeHeaders.forEach((aColumn, index) => {
        if(Object.keys(aColumn).length === 0) {
            payTypeHeaders.splice(index, 1);
        }
    })

    return {payTypeHeaders}
}

ReportUtils.getPayTypeValues = function(employeePayments, payTypeHeaders) {
    let payTypeValues = []

    employeePayments.forEach(anEmployeeData => {
        let aRowOfPayTypeValues = []

        payTypeHeaders.forEach(aPaytypeHeader => {
            if(aPaytypeHeader === 'EMP ID') {
                let employee = Meteor.users.findOne({_id: anEmployeeData.employeeId});
                aRowOfPayTypeValues.push(employee.employeeProfile.employeeId)
                return
            }
            if(aPaytypeHeader === 'Employee') {
                let employee = Meteor.users.findOne({_id: anEmployeeData.employeeId});
                aRowOfPayTypeValues.push(employee.profile.fullName)
                return
            }
            //--
            let payDetails = _.find(anEmployeeData.payment, function(aPayType) {
                return aPayType.id && (aPaytypeHeader.id === aPayType.id && aPaytypeHeader.code === aPayType.code)
            })
            if(payDetails) {
                let payAmount = payDetails.amountLC
                if(payDetails.amountLC != payDetails.amountPC) {
                    payAmount = payDetails.amountPC
                }
                if(payDetails.type === 'Deduction') {
                    if(payAmount > 0) {
                        payAmount *= -1
                    }
                }
                aRowOfPayTypeValues.push(payAmount)
            } else if(aPaytypeHeader.id === 'netPay') {

                let netPay = _.find(anEmployeeData.payment, function(aPayType) {
                    return (aPayType.code === 'NMP')
                })
                if(netPay) {
                    let payAmount = netPay.amountLC
                    if(netPay.amountLC != netPay.amountPC) {
                        payAmount = netPay.amountPC
                    }
                    aRowOfPayTypeValues.push(payAmount)
                }
            } else if(aPaytypeHeader.id === 'totalDeduction') {
                let totalDeduction = _.find(anEmployeeData.payment, function(aPayType) {
                    return (aPayType.code === 'TDEDUCT')
                })
                if(totalDeduction) {
                    let payAmount = totalDeduction.amountLC
                    if(totalDeduction.amountLC != totalDeduction.amountPC) {
                        payAmount = totalDeduction.amountPC
                    }
                    aRowOfPayTypeValues.push(payAmount)
                }
            } else {
                aRowOfPayTypeValues.push("---")
            }
        })
        payTypeValues.push(aRowOfPayTypeValues)
    })
    return payTypeValues
}

Template.PayrunNew.events({
    'click #startProcessing': (e,tmpl) => {
        let payGrades = function(){
            let selected = [];
            $("input:checkbox[name=paygrades]:checked").each(function () {
                selected.push($(this).attr("id"));
            });
            return selected;
        };
        let annualPay = function(){
            let selected = [];
            $("input:checkbox[name=selectAnnual]:checked").each(function () {
                selected.push($(this).attr("id"));
            });
            console.log(selected);
            return selected;
        };
        var view = Blaze.render(Template.Loading, document.getElementById('spinner'));
        const params = {
            employees: Core.returnSelection($('[name="employee"]')),
            paygrades: payGrades(),
            period: {
                month: $('[name="paymentPeriod.month"]').val(),
                year: $('[name="paymentPeriod.year"]').val(),

            },
            type: $('input[name=runtype]:checked').val(),
            annuals: annualPay()
        };
        //check if any valid selection is made
        if (params.employees.length > 0 || params.paygrades.length > 0){
            Meteor.call("payrun/process",  params, Session.get('context'), (err, res) => {
                if(res){
                    // console.log(`Payrun results: ${JSON.stringify(res)}`);
                    Session.set('currentPayrunPeriod', res.period);

                    //set result as reactive dict payResult
                    tmpl.dict.set("payResult", res);
                } else{
                    console.log(err);
                }
                Blaze.remove(view);
            })
        } else {
            Blaze.remove(view);
            swal("notice","A valid selection must be made", "error");
        }

    },
    'change [name="annualPay"]': (e, tmpl) => {
        tmpl.includePay.set($(e.target).is(':checked'));

    },
    'change [name="employee"]': (e, tmpl) => {
        let selected = Core.returnSelection($(e.target));
        selected.length > 0 ? tmpl.showPayGrade.set(false):tmpl.showPayGrade.set(true);
        //add employee paygrades
        let employeeGrade = selected.map(x => {
            return Meteor.users.findOne({_id: x, 'employee': true}).employeeProfile.employment.paygrade;
        });
        tmpl.grades.set(employeeGrade);
    },
    'change [name="paygrades"]': (e, tmpl) => {
        let selected = [];
        $("input:checkbox[name=paygrades]:checked").each(function () {
            selected.push($(this).attr("id"));
        });
        tmpl.grades.set(selected);
    },
    'hover .table tbody tr': (e,tmpl) => {
        console.log('hover called');
    },
    selectedMonth: function (val) {
        if(Template.instance().selectedMonth.get()) {
            return Template.instance().selectedMonth.get() === val ? selected="selected" : '';
        }
    },
    selectedYear: function (val) {
        if(Template.instance().selectedYear.get()) {
            return Template.instance().selectedYear.get() === val ? selected="selected" : '';
        }
    },
    'click #export-to-csv': (e,tmpl) => {
        const payResult = Template.instance().dict.get('payResult');
        if (payResult) {
            console.log(`payResult(exportToCsv): `, payResult)

            let payTypeHeaders = ReportUtils.getPayTypeHeaders2(payResult.payObj.payrun)

            let formattedHeader = payTypeHeaders.payTypeHeaders.map(aHeader => {
                return aHeader.description || aHeader
            })
            //--
            let reportData = ReportUtils.getPayTypeValues(payResult.payObj.payrun, payTypeHeaders.payTypeHeaders)

            BulkpayExplorer.exportAllData({fields: formattedHeader, data: reportData},
                `Payrun results export`);
        } else {
            swal('Error', 'No payrun results', 'error')
        }
    }
});

/*****************************************************************************/
/* PayrunNew: Helpers */
/*****************************************************************************/
Template.PayrunNew.helpers({
    'month': function(){
        return Core.months()
    },
    'year': function(){
        return Core.years();
    },
    'paygrades': () => {
       return PayGrades.find();
    },
    'employees': () => {
        return Meteor.users.find({"employee": true});
    },
    "showPayGrade": () => {
        return Template.instance().showPayGrade.get();
    },
    "showAnnualPay": () => {
        return Template.instance().includePay.get();
    },
    'annualPay': () => {
        return PayTypes.find().fetch();
    },
    'checkInitial': (index) => {
        return index === 0 ? 'checked': null;
    },
    'nopayresult': () =>{
        const payresult = Template.instance().dict.get('payResult');
        if (!payresult) return true;
        return false;
    },
    'employeeResult': () => {
        const payResult = Template.instance().dict.get('payResult');
        if (payResult) {
            console.log(`pay result`, payResult.payObj.result)
            return payResult.payObj.result;
        }
    },
    'processingError': () => {
        const payresult = Template.instance().dict.get('payResult');
        return payresult.payObj.error.length;
    },
    selectedMonth: function (val) {
        if(Template.instance().selectedMonth.get()) {
            return Template.instance().selectedMonth.get() === val ? selected="selected" : '';
        }
    },
    selectedYear: function (val) {
        if(Template.instance().selectedYear.get()) {
            return Template.instance().selectedYear.get() === val ? selected="selected" : '';
        }
    }
});

/*****************************************************************************/
/* Payments: Lifecycle Hooks */
/*****************************************************************************/
Template.PayrunNew.onCreated(function () {
    let self = this;
    self.subscribe("paygrades", Session.get('context'));
    self.subscribe("activeEmployees", Session.get('context'));
    self.dict = new ReactiveDict();
    self.showPayGrade = new ReactiveVar(true);
    self.grades = new ReactiveVar([]);
    self.includePay = new ReactiveVar(false);
    // if annual payment included, subscribe to all annual pay.

    //--
    self.selectedMonth = new ReactiveVar();
    self.selectedYear = new ReactiveVar();
    //--
    let theMoment = moment();
    self.selectedMonth.set(theMoment.format('MM'))
    self.selectedYear.set(theMoment.format('YYYY'))
    //--

    self.autorun(function(){
        let includeType = self.includePay.get();
        let selectedGrade = self.grades.get();
        if (includeType)
            self.subscribe("AdditionalPayTypes", selectedGrade, Session.get('context'));
    })
});

Template.PayrunNew.onRendered(function () {
     $('select.dropdown').dropdown();
    // //
    // let selected = [];
    // $("input:checkbox[name=paygrades]:checked").each(function () {
    //     selected.push($(this).attr("id"));
    // });
    // Template.instance().grades.set(selected);
    // Show aciton upon row hover
});

Template.PayrunNew.onDestroyed(function () {
});
<|endoftext|>


<commit_before>/* @flow */
import makeClassName from 'classnames';
import { withRouter } from 'react-router-dom';
import * as React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';

import {
  CLIENT_APP_ANDROID,
  INCOMPATIBLE_ANDROID_UNSUPPORTED,
  INCOMPATIBLE_FIREFOX_FOR_IOS,
} from 'core/constants';
import translate from 'core/i18n/translate';
import { sanitizeHTML } from 'core/utils';
import {
  correctedLocationForPlatform,
  getClientCompatibility,
  isFenix,
} from 'core/utils/compatibility';
import Notice, { warningInfoType } from 'ui/components/Notice';
import type { AppState } from 'amo/store';
import type { AddonType } from 'core/types/addons';
import type { UserAgentInfoType } from 'core/reducers/api';
import type { AddonVersionType } from 'core/reducers/versions';
import type { I18nType } from 'core/types/i18n';
import type { ReactRouterLocationType } from 'core/types/router';

import './styles.scss';

export const FENIX_LINK_DESTINATION =
  'https://support.mozilla.org/kb/add-compatibility-firefox-preview/';

type Props = {|
  addon?: AddonType | null,
  className?: string,
  currentVersion?: AddonVersionType | null,
  fixAndroidLinkMessage?: string,
  fixFirefoxLinkMessage?: string,
  fixFenixLinkMessage?: string,
|};

type InternalProps = {|
  ...Props,
  _correctedLocationForPlatform: typeof correctedLocationForPlatform,
  _getClientCompatibility: typeof getClientCompatibility,
  _isFenix: typeof isFenix,
  clientApp: string,
  i18n: I18nType,
  location: ReactRouterLocationType,
  userAgentInfo: UserAgentInfoType,
|};

export class WrongPlatformWarningBase extends React.Component<InternalProps> {
  static defaultProps = {
    _correctedLocationForPlatform: correctedLocationForPlatform,
    _getClientCompatibility: getClientCompatibility,
    _isFenix: isFenix,
  };

  render() {
    const {
      _correctedLocationForPlatform,
      _getClientCompatibility,
      _isFenix,
      addon,
      className,
      clientApp,
      currentVersion,
      i18n,
      location,
      userAgentInfo,
    } = this.props;

    let message;

    const newLocation = _correctedLocationForPlatform({
      clientApp,
      location,
      userAgentInfo,
    });

    if (_isFenix(userAgentInfo)) {
      message = i18n.sprintf(
        this.props.fixFenixLinkMessage ||
          i18n.gettext(
            `To learn about add-ons compatible with Firefox for Android,
               click here.`,
          ),
        { newLocation: FENIX_LINK_DESTINATION },
      );
    } else if (newLocation) {
      const fixAndroidLinkMessage =
        this.props.fixAndroidLinkMessage ||
        i18n.gettext(
          `To find add-ons compatible with Firefox on Android,
               visit our mobile site.`,
        );

      const fixFirefoxLinkMessage =
        this.props.fixFirefoxLinkMessage ||
        i18n.gettext(
          `To find add-ons compatible with Firefox on desktop,
               visit our desktop site.`,
        );

      message =
        clientApp === CLIENT_APP_ANDROID
          ? i18n.sprintf(fixFirefoxLinkMessage, { newLocation })
          : i18n.sprintf(fixAndroidLinkMessage, { newLocation });
    }

    // Check for an add-on that is incompatible on Android.
    if (addon && currentVersion) {
      const compatibility = _getClientCompatibility({
        addon,
        clientApp,
        currentVersion,
        userAgentInfo,
      });
      if (compatibility.reason === INCOMPATIBLE_ANDROID_UNSUPPORTED) {
        message = i18n.sprintf(
          i18n.gettext(
            `Not available on Firefox for Android. You can use this add-on with Firefox for Desktop,
              or look for similar Android add-ons.`,
          ),
          { newLocation: '/android/' },
        );
      } else if (compatibility.reason === INCOMPATIBLE_FIREFOX_FOR_IOS) {
        message = i18n.gettext(
          `This add-on is not compatible with this browser. Try installing it on Firefox for desktop.`,
        );
      }
    }

    return message ? (

        <Notice id="WrongPlatformWarning-Notice" type={warningInfoType}>

        </Notice>

    ) : null;
  }
}

export function mapStateToProps(state: AppState) {
  return {
    clientApp: state.api.clientApp,
    userAgentInfo: state.api.userAgentInfo,
  };
}

const WrongPlatformWarning: React.ComponentType<Props> = compose(
  withRouter,
  connect(mapStateToProps),
  translate(),
)(WrongPlatformWarningBase);

export default WrongPlatformWarning;
<commit_msg>android target link fixed (#9599)<commit_after>/* @flow */
import makeClassName from 'classnames';
import { withRouter } from 'react-router-dom';
import * as React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';

import {
  CLIENT_APP_ANDROID,
  INCOMPATIBLE_ANDROID_UNSUPPORTED,
  INCOMPATIBLE_FIREFOX_FOR_IOS,
} from 'core/constants';
import translate from 'core/i18n/translate';
import { sanitizeHTML } from 'core/utils';
import {
  correctedLocationForPlatform,
  getClientCompatibility,
  isFenix,
} from 'core/utils/compatibility';
import Notice, { warningInfoType } from 'ui/components/Notice';
import type { AppState } from 'amo/store';
import type { AddonType } from 'core/types/addons';
import type { UserAgentInfoType } from 'core/reducers/api';
import type { AddonVersionType } from 'core/reducers/versions';
import type { I18nType } from 'core/types/i18n';
import type { ReactRouterLocationType } from 'core/types/router';

import './styles.scss';

export const FENIX_LINK_DESTINATION =
  'https://support.mozilla.org/kb/find-and-install-add-ons-firefox-android';

type Props = {|
  addon?: AddonType | null,
  className?: string,
  currentVersion?: AddonVersionType | null,
  fixAndroidLinkMessage?: string,
  fixFirefoxLinkMessage?: string,
  fixFenixLinkMessage?: string,
|};

type InternalProps = {|
  ...Props,
  _correctedLocationForPlatform: typeof correctedLocationForPlatform,
  _getClientCompatibility: typeof getClientCompatibility,
  _isFenix: typeof isFenix,
  clientApp: string,
  i18n: I18nType,
  location: ReactRouterLocationType,
  userAgentInfo: UserAgentInfoType,
|};

export class WrongPlatformWarningBase extends React.Component<InternalProps> {
  static defaultProps = {
    _correctedLocationForPlatform: correctedLocationForPlatform,
    _getClientCompatibility: getClientCompatibility,
    _isFenix: isFenix,
  };

  render() {
    const {
      _correctedLocationForPlatform,
      _getClientCompatibility,
      _isFenix,
      addon,
      className,
      clientApp,
      currentVersion,
      i18n,
      location,
      userAgentInfo,
    } = this.props;

    let message;

    const newLocation = _correctedLocationForPlatform({
      clientApp,
      location,
      userAgentInfo,
    });

    if (_isFenix(userAgentInfo)) {
      message = i18n.sprintf(
        this.props.fixFenixLinkMessage ||
          i18n.gettext(
            `To learn about add-ons compatible with Firefox for Android,
               click here.`,
          ),
        { newLocation: FENIX_LINK_DESTINATION },
      );
    } else if (newLocation) {
      const fixAndroidLinkMessage =
        this.props.fixAndroidLinkMessage ||
        i18n.gettext(
          `To find add-ons compatible with Firefox on Android,
               visit our mobile site.`,
        );

      const fixFirefoxLinkMessage =
        this.props.fixFirefoxLinkMessage ||
        i18n.gettext(
          `To find add-ons compatible with Firefox on desktop,
               visit our desktop site.`,
        );

      message =
        clientApp === CLIENT_APP_ANDROID
          ? i18n.sprintf(fixFirefoxLinkMessage, { newLocation })
          : i18n.sprintf(fixAndroidLinkMessage, { newLocation });
    }

    // Check for an add-on that is incompatible on Android.
    if (addon && currentVersion) {
      const compatibility = _getClientCompatibility({
        addon,
        clientApp,
        currentVersion,
        userAgentInfo,
      });
      if (compatibility.reason === INCOMPATIBLE_ANDROID_UNSUPPORTED) {
        message = i18n.sprintf(
          i18n.gettext(
            `Not available on Firefox for Android. You can use this add-on with Firefox for Desktop,
              or look for similar Android add-ons.`,
          ),
          { newLocation: '/android/' },
        );
      } else if (compatibility.reason === INCOMPATIBLE_FIREFOX_FOR_IOS) {
        message = i18n.gettext(
          `This add-on is not compatible with this browser. Try installing it on Firefox for desktop.`,
        );
      }
    }

    return message ? (

        <Notice id="WrongPlatformWarning-Notice" type={warningInfoType}>

        </Notice>

    ) : null;
  }
}

export function mapStateToProps(state: AppState) {
  return {
    clientApp: state.api.clientApp,
    userAgentInfo: state.api.userAgentInfo,
  };
}

const WrongPlatformWarning: React.ComponentType<Props> = compose(
  withRouter,
  connect(mapStateToProps),
  translate(),
)(WrongPlatformWarningBase);

export default WrongPlatformWarning;
<|endoftext|>


<commit_before>'use strict';

angular.module('csyywx')
	.controller('BalanceCtrl', function($scope, $stateParams, AccountApi, userConfig, balanceService, $filter) {
		var queryType = 0;
		switch($stateParams.type) {
			case 'accumulative':
				$scope.title = '累计收益';
				$scope.balance = balanceService.balance.totalIncome;
				break;
			case 'total':
				$scope.title = '总资产';
				queryType = 1;
				$scope.balance = balanceService.balance.total;
				break;
			case 'regular':
				$scope.title = '定期资产';
				queryType = 2;
				$scope.balance = balanceService.balance.regular;
				break;
			case 'demand':
				$scope.title = '活期资产';
				queryType = 3;
				$scope.balance = balanceService.balance.demand;
				break;
		}

		if(queryType) {
			AccountApi.getBalanceDetail({
				sessionId: userConfig.getSessionId(),
				queryType: queryType
			}).success(function(data) {
				if(+data.flag === 1) {
					$scope.items = data.data.assetList.map(function(obj) {
						var status, style;
						switch(+obj.status) {
							case 1:
								status = '转活期';
								style = 'line-through';
								break;
							case 2:
								status = '提现';
								break;
							case 3:
								status = '转定期';
								break;
							default:
								status = '买入';
						}

						if(+queryType === 3) {
							status = '成功';
						}

						return {
							date: $filter('date')(new Date(obj.investDate), 'yyyy-MM-dd'),
							desc: obj.termDesc,
							amount: obj.investAmount,
							status: status,
							style: style
						};
					});
				}
			});
		} else {
			AccountApi.getLatestIncome({sessionId: userConfig.getSessionId()})
				.success(function(data) {
					if(+data.flag === 1) {
						$scope.items = data.data.interestList.map(function(obj) {
							return {
								date: obj.interestDate,
								amount: obj.actualInterest
							}
						})
					}
				})
		}

	})<commit_msg>remand balance detail<commit_after>'use strict';

angular.module('csyywx')
	.controller('BalanceCtrl', function($scope, $stateParams, AccountApi, userConfig, balanceService, $filter) {
		var queryType = 0;
		switch($stateParams.type) {
			case 'accumulative':
				$scope.title = '累计收益';
				$scope.balance = balanceService.balance.totalIncome;
				break;
			case 'total':
				$scope.title = '总资产';
				queryType = 1;
				$scope.balance = balanceService.balance.total;
				break;
			case 'regular':
				$scope.title = '定期资产';
				queryType = 2;
				$scope.balance = balanceService.balance.regular;
				break;
			case 'demand':
				$scope.title = '活期资产';
				queryType = 3;
				$scope.balance = balanceService.balance.demand;
				break;
		}

		if(queryType) {
			AccountApi.getBalanceDetail({
				sessionId: userConfig.getSessionId(),
				queryType: queryType
			}).success(function(data) {
				if(+data.flag === 1) {
					$scope.items = data.data.assetList.map(function(obj) {
						var status, style;
						switch(+obj.status) {
							case 1:
								status = '转活期';
								style = 'line-through';
								break;
							case 2:
								status = '提现';
								break;
							case 3:
								status = '转定期';
								style = 'line-through';
								break;
							default:
								status = '买入';
						}

						if(+queryType === 3 && +obj.status !== 3) {
							status = '成功';
						}

						return {
							date: $filter('date')(new Date(obj.investDate), 'yyyy-MM-dd'),
							desc: obj.termDesc,
							amount: obj.investAmount,
							status: status,
							style: style
						};
					});
				}
			});
		} else {
			AccountApi.getLatestIncome({sessionId: userConfig.getSessionId()})
				.success(function(data) {
					if(+data.flag === 1) {
						$scope.items = data.data.interestList.map(function(obj) {
							return {
								date: obj.interestDate,
								amount: obj.actualInterest
							}
						})
					}
				})
		}

	})<|endoftext|>