Source: kit.js

/**
 * The Kit module
 * Kit class inherits from Base so it supports KeyValues (Attachment, Comment)
 * @module kit
 * @copyright CHECKROOM NV 2015
 */
define([
    'jquery',
    'base',
    'common'], /** @lends Kit */ function ($, Base, common) {

    var DEFAULTS = {
        name: "",
        items: [],
        status: "unknown",
        cover: ""
    };

    // Allow overriding the ctor during inheritance
    // http://stackoverflow.com/questions/4152931/javascript-inheritance-call-super-constructor-or-use-prototype-chain
    var tmp = function() {};
    tmp.prototype = Base.prototype;

    /**
     * Kit class
     * @name  Kit
     * @class
     * @constructor
     * @extends Base
     */
    var Kit = function(opt) {
        var spec = $.extend({
            _fields: ['*'],
            crtype: 'cheqroom.types.kit'
        }, opt);
        Base.call(this, spec);

        this.name = spec.name || DEFAULTS.name;
        this.items = spec.items || DEFAULTS.items.slice();
        this.codes = [];
        this.conflicts = [];
        this.status = spec.status || DEFAULTS.status;
        this.cover = spec.cover || DEFAULTS.cover;
    };

    Kit.prototype = new tmp();
    Kit.prototype.constructor = Kit;

    //
    // Specific validators
    /**
     * Checks if name is valid
     * @name Kit#isValidName
     * @method
     * @return {Boolean} 
     */
    Kit.prototype.isValidName = function() {
        this.name = $.trim(this.name);
        return (this.name.length>=3);
    };

    /**
     * Check if name is valid and isn't already used
     * @name Kit#isValidNameAsync
     * @method
     * @return {promise} 
     */
    Kit.prototype.isNameAvailableAsync = function(){
        // When existing kit is edited, we don't want 
        // to check its current name 
        if(this.id != null && this.raw != null && this.name == this.raw.name){
            return $.Deferred().resolve(true);
        }

        // If a previous name available check is pending, abort it
        if(this._dfdNameAvailable){ this._dfdNameAvailable.abort(); }

        this._dfdNameAvailable = this.ds.search({name: $.trim(this.name) }, "_id");
            
        return this._dfdNameAvailable.then(function(resp) {
            return resp.count == 0;
        }, function(error) {
            return false;
        });
    };

    //
    // Base overrides
    //

    /**
     * Checks if the Kit has any validation errors
     * @name Kit#isValid
     * @method
     * @returns {boolean}
     * @override
     */
    Kit.prototype.isValid = function() {
        return this.isValidName();
    };

    /**
     * Checks if the kit is empty
     * @name Kit#isEmpty
     * @returns {boolean}
     */
    Kit.prototype.isEmpty = function() {
        // Checks for: name
        return (
            (Base.prototype.isEmpty.call(this)) &&
            (this.name==DEFAULTS.name));
    };

    /**
     * Checks if the Kits is dirty and needs saving
     * @name Kit#isDirty
     * @returns {boolean}
     * @override
     */
    Kit.prototype.isDirty = function() {
        var isDirty = Base.prototype.isDirty.call(this);
        if( (!isDirty) &&
            (this.raw)) {
            isDirty = this._isDirtyStringProperty("name");
        }
        return isDirty;
    };

    //
    // Business logic
    //
    // KIT_STATUS = ( 'available', 'checkedout', 'await_checkout', 'in_transit', 'maintenance', 'repair', 'inspection', 'expired', 'in_custody', 'empty', 'incomplete')

    /**
     * Checks if a Kit can be checked out (based on status)
     * @name Kit#canCheckout
     * @method
     * @returns {boolean}
     */
    Kit.prototype.canCheckout = function() {
        return common.kitCanCheckout(this);
    };

    /**
     * Checks if a Kit can be reserved (based on status)
     * @name Kit#canReserve
     * @method
     * @returns {boolean}
     */
    Kit.prototype.canReserve = function() {
        return common.kitCanReserve(this);
    };

    /**
     * addItems; adds a bunch of Items to the transaction using a list of item ids
     * @name Kit#addItems
     * @method
     * @param items
     * @param skipRead
     * @returns {promise}
     */
    Kit.prototype.addItems = function(items, skipRead) {
        if (!this.existsInDb()) {
            return $.Deferred().reject(new Error("Cannot addItems from document without id"));
        }

        return this._doApiCall({
            method: 'addItems',
            params: {items: items},
            skipRead: skipRead
        });
    };

    /**
     * removeItems; removes a bunch of Items from the transaction using a list of item ids
     * @name Kit#removeItems
     * @method
     * @param items (can be null)
     * @param skipRead
     * @returns {promise}
     */
    Kit.prototype.removeItems = function(items, skipRead) {
        if (!this.existsInDb()) {
            return $.Deferred().reject(new Error("Cannot removeItems from document without id"));
        }

        return this._doApiCall({
            method: 'removeItems',
            params: {items: items},
            skipRead: skipRead
        });
    };

    /**
     * moveItem; moves an Item in a kit to another position
     * @name Kit#moveItem
     * @method
     * @param item
     * @param toPos
     * @param skipRead
     * @returns {promise}
     */
    Kit.prototype.moveItem = function(item, toPos, skipRead) {
        if (!this.existsInDb()) {
            return $.Deferred().reject(new Error("Cannot moveItem from document without id"));
        }

        return this._doApiCall({
            method: 'moveItem',
            params: {item: item, toPos: toPos},
            skipRead: skipRead
        });
    };

    /**
     * Adds a QR code to the kit
     * @name Kit#addCode
     * @param code
     * @param skipRead
     * @returns {promise}
     */
    Kit.prototype.addCode = function(code, skipRead) {
        return this._doApiCall({method: 'addCodes', params: {codes: [code]}, skipRead: skipRead});
    };

    /**
     * Removes a QR code from the kit
     * @name Kit#removeCode
     * @param code
     * @param skipRead
     * @returns {promise}
     */
    Kit.prototype.removeCode = function(code, skipRead) {
        return this._doApiCall({method: 'removeCodes', params: {codes: [code]}, skipRead: skipRead});
    };

    /**
     * Duplicates an item a number of times
     * @name Kit#duplicate
     * @param  {int} times
     * @return {promise}      
     */
    Kit.prototype.duplicate = function(times, location, skipRead){
        return this._doApiCall({method: 'duplicate', params: {times: times, location: location}, skipRead: skipRead || true });
    };

    /**
     * Checks if custody can be taken for a kit (based on status)
     * @name Kit#canTakeCustody
     * @returns {boolean}
     */
    Kit.prototype.canTakeCustody = function() {
        return common.kitCanTakeCustody(this);
    };

    /**
     * Checks if custody can be released for a kit (based on status)
     * @name Kit#canReleaseCustody
     * @returns {boolean}
     */
    Kit.prototype.canReleaseCustody = function() {
        return common.kitCanReleaseCustody(this);
    };

    /**
     * Checks if custody can be transferred for a kit (based on status)
     * @name Kit#canTransferCustody
     * @returns {boolean}
     */
    Kit.prototype.canTransferCustody = function() {
        return common.kitCanTransferCustody(this);
    };

    /**
     * Takes custody of a kit (and all items in it)
     * Puts it in the *in_custody* status
     * @name Kit#takeCustody
     * @param customerId (when null, we'll take the customer of the user making the API call)
     * @param skipRead
     * @returns {promise}
     */
    Kit.prototype.takeCustody = function(customerId, skipRead) {
        return this._doApiCall({method: 'takeCustody', params: {customer: customerId}, skipRead: skipRead});
    };

    /**
     * Releases custody of a kit (and all items in it) at a certain location
     * Puts it in the *available* status again
     * @name Kit#releaseCustody
     * @param locationId
     * @param skipRead
     * @returns {promise}
     */
    Kit.prototype.releaseCustody = function(locationId, skipRead) {
        return this._doApiCall({method: 'releaseCustody', params: {location: locationId}, skipRead: skipRead});
    };

    /**
     * Transfers custody of a kit (and all items in it)
     * Keeps it in the *in_custody* status
     * @name Kit#transferCustody
     * @param customerId (when null, we'll take the customer of the user making the API call)
     * @param skipRead
     * @returns {promise}
     */
    Kit.prototype.transferCustody = function(customerId, skipRead) {
        return this._doApiCall({method: 'transferCustody', params: {customer: customerId}, skipRead: skipRead});
    };

    //
    // Implementation stuff
    //

    Kit.prototype._getDefaults = function() {
        return DEFAULTS;
    };

    Kit.prototype._toJson = function(options) {
        var data = Base.prototype._toJson.call(this, options);
        data.name = this.name || DEFAULTS.name;
        //data.items --> not via update
        return data;
    };

    Kit.prototype._fromJson = function(data, options) {
        var that = this;
        return Base.prototype._fromJson.call(this, data, options)
            .then(function(data) {
                that.name = data.name || DEFAULTS.name;
                that.items = data.items || DEFAULTS.items.slice();
                that.codes = data.codes || [];
                that.status = data.status || DEFAULTS.status;
                that.cover = data.cover || DEFAULTS.cover;

                that._loadConflicts(that.items);

                $.publish('Kit.fromJson', data);
                return data;
            });
    };

    // Override create method so we can pass items
    // We don't override _toJson to include items, because this would
    // mean that on an update items would also be passed
    Kit.prototype.create = function(skipRead){
        if (this.existsInDb()) {
            return $.Deferred().reject(new Error("Cannot create document, already exists in database"));
        }
        
        // Don't check for isEmpty/isValid, if no name is given,
        // that we automatically generate a name on the server
        //if (this.isEmpty()) {
        //    return $.Deferred().reject(new Error("Cannot create empty document"));
        //}
        //if (!this.isValid()) {
        //    return $.Deferred().reject(new Error("Cannot create, invalid document"));
        //}

        var that = this,
            data = {
            name: this.name,
            items: this._getIds(this.items)
        };

        // Also add any possible fields we need during `create`
        $.extend(data, this._toJsonFields());

        delete data.id;

        return this.ds.create(data, this._fields)
            .then(function(data) {
                return (skipRead==true) ? data : that._fromJson(data);
            });
    };

    Kit.prototype._loadConflicts = function(items) {
        var conflicts = [];
        var kitStatus = common.getKitStatus(items);
        
        // Kit has only conflicts when it's status is incomplete  
        if(kitStatus == "incomplete") {
            $.each(items, function(i, item){
                 switch(item.status){
                    case "await_checkout":
                        conflicts.push({
                            kind: "status",
                            item: item._id,
                            itemName: item.name,
                            itemStatus: item.status,
                            order: item.order
                        });
                        break;
                    case "checkedout":
                        conflicts.push({
                            kind: "order",
                            item: item._id,
                            itemName: item.name,
                            itemStatus: item.status,
                            order: item.order
                        });
                        break;
                    case "expired":
                        conflicts.push({
                            kind: "status",
                            item: item._id,
                            itemName: item.name,                            
                            itemStatus: item.status
                        });
                        break;
                    case "in_custody":
                        conflicts.push({
                            kind: "custody",
                            item: item._id,
                            itemName: item.name,                            
                            itemStatus: item.status
                        });
                        break;
                }
            });
        }
            
        this.conflicts = conflicts;
    };

    return Kit;

});