diff --git a/js/core/view/shipping/csvcontainerspreadsheet.js b/js/core/view/shipping/csvcontainerspreadsheet.js new file mode 100644 index 000000000..02bf60232 --- /dev/null +++ b/js/core/view/shipping/csvcontainerspreadsheet.js @@ -0,0 +1,639 @@ +function CSVContainerSpreadSheet(args){ + this.id = BUI.id(); + + + this.cells = function(){ + cellProperties.renderer = function(instance, td, row, col, prop, value, cellProperties){ + Handsontable.renderers.TextRenderer.apply(this, arguments); + td.style.fontWeight = 'bold'; + td.style.color = 'green'; + td.style.background = '#CEC'; + } + } + + args.cells = this.cells; + + + SpreadSheet.call(this, args); + + /** Cache to store crystal indexed by protein acronym this.crystals["acroynm"] -> last crystal */ + this.crystals = {}; + + /** Cache of proteins of the sessions */ + this.proteins = {}; + /** Array of arrays with the list of crystal form by protein acronym */ + this.crystalFormList = {}; + + this.renderCrystalFormColumn = false; + + if (args != null) { + if (args.renderCrystalFormColumn != null) { + this.renderCrystalFormColumn = args.renderCrystalFormColumn; + } + } + + this.crystalInfoToIdMap = {}; + + this.crystalFormIndex = -1; + // this.unitCellIndex = -1; + this.spaceGroupIndex = -1; + + this.onModified = new Event(this); + + this.count = 0; + + /** Colors for parcels */ + this.cellColorBackground = this.getParcelColors(); + + this.parcelColorBackground = {}; + + /** When getPanel it will load all the samples for this proposal */ + this.proposalSamples = []; + + /** Controlled list of values */ + this.containerTypeControlledList = [ + { name:"Unipuck", capacity: 16 }, + { name:"Spinepuck", capacity:10 } + ]; + /** dewars names that already exist on this shipment. This object is supposed to be a SET */ + this.dewarNameControlledList = new Set(); + this.containerNameControlledList = new Set(); + + /** It validates the data */ + this.puckValidator = new PuckValidator(); + + /** Table Indices */ + this.PARCELNAME_INDEX = 0; + this.CONTAINERNAME_INDEX = 1; + this.CONTAINERTYPE_INDEX = 2; + this.PROTEINACRONYM_INDEX = 4; +} + +CSVContainerSpreadSheet.prototype.getPanel = SpreadSheet.prototype.getPanel; +CSVContainerSpreadSheet.prototype.setLoading = SpreadSheet.prototype.setLoading; +CSVContainerSpreadSheet.prototype.getAcronyms = SpreadSheet.prototype.getAcronyms; +CSVContainerSpreadSheet.prototype.getHeaderWidth = SpreadSheet.prototype.getHeaderWidth; +CSVContainerSpreadSheet.prototype.getHeaderId = SpreadSheet.prototype.getHeaderId; +CSVContainerSpreadSheet.prototype.getHeaderText = SpreadSheet.prototype.getHeaderText; +CSVContainerSpreadSheet.prototype.getColumns = SpreadSheet.prototype.getColumns; +CSVContainerSpreadSheet.prototype.getData = SpreadSheet.prototype.getData; +CSVContainerSpreadSheet.prototype.setDataAtCell = SpreadSheet.prototype.setDataAtCell; +CSVContainerSpreadSheet.prototype.getColumnIndex = SpreadSheet.prototype.getColumnIndex; +CSVContainerSpreadSheet.prototype.disableAll = SpreadSheet.prototype.disableAll; +CSVContainerSpreadSheet.prototype.setContainerType = SpreadSheet.prototype.setContainerType; +CSVContainerSpreadSheet.prototype.updateNumberOfRows = SpreadSheet.prototype.updateNumberOfRows; +CSVContainerSpreadSheet.prototype.emptyRow = SpreadSheet.prototype.emptyRow; +CSVContainerSpreadSheet.prototype.parseTableData = ContainerSpreadSheet.prototype.parseTableData; +CSVContainerSpreadSheet.prototype.disableAll = ContainerSpreadSheet.prototype.disableAll; + +/** +* This checks all rows and validate the data +* +* @method validateData +* @return {Boolean} Return true if data is valid or false otherwise +*/ +CSVContainerSpreadSheet.prototype.validateData = function() { + var data = this.spreadSheet.getData(); + debugger + for (var i = 0; i< data.length; i++){ + this.validateRow(data[i]); + } +}; + +/** +* This checks all rows and validate the data +* +* @method validateData +* @return {Boolean} Return true if data is valid or false otherwise +*/ +CSVContainerSpreadSheet.prototype.validateRow = function(row) { + debugger + var parcelName = row[this.PARCELNAME_INDEX]; + if (this.isParcelNameValid(parcelName)){ + if (this.isContinaer)xx + } + return false; +}; + + + +/** +* Returns a list of objects that will contain colors for parent and containers nodes +* +* @method getParcelColors +*/ +CSVContainerSpreadSheet.prototype.getParcelColors = function(){ + return [{"color": "#0099ff", "containers" : [{"color":"#4db8ff"}, {"color":"#80ccff"}, {"color":"#b3e0ff"}, {"color":"#e6f5ff"}]}, + {"color": "#33cc33", "containers" : [{"color":"#5cd65c"},{"color":"#85e085"},{"color":"#adebad"},{"color":"#d6f5d6#"}]}, + {"color": "#ffbb33", "containers" : [{"color":"#ffcc66"},{"color":"#ffdd99"},{"color":"#ffeecc"}, {"color":"#fff7e6"}]}, + {"color": "#bb33ff", "containers" : [{"color":"#cc66ff"}, {"color":"#dd99ff"},{"color":"#e6b3ff"}, {"color":"#eeccff"}]} + ]; +}; + +CSVContainerSpreadSheet.prototype.loadData = function(data){ + + var _this = this; + function firstRowRenderer(instance, td, row, col, prop, value, cellProperties) { + Handsontable.renderers.TextRenderer.apply(this, arguments); + td.style.fontWeight = 'bold'; + td.style.color = 'green'; + td.style.fontSize = '9px'; + td.style.background = '#CEC'; + } + + function ValueRenderer(instance, td, row, col, prop, value, cellProperties) { + Handsontable.renderers.TextRenderer.apply(this, arguments); + } + + // maps function to lookup string + Handsontable.renderers.registerRenderer('ValueRenderer', ValueRenderer); + this.spreadSheet = new Handsontable( + document.getElementById(this.id + '_samples'), { + afterCreateRow: function (index, numberOfRows) { + data.splice(index, numberOfRows); + }, + beforeChange: function (changes, source) { + lastChange = changes; + }, + afterChange: function (changes, source) { + }, + cells: function (row, col, prop) { + }, + data: data, + height : this.height, + width : this.width, + manualColumnResize: true, + colWidths: this.getHeaderWidth(), + colHeaders: this.getHeaderText(), + stretchH: 'last', + columns: this.getColumns(), + }); +} + +/** Parcels and dewars are the same */ +CSVContainerSpreadSheet.prototype.getParcelsByRows = function(rows) { + return _.groupBy(rows, "parcel"); +}; + +CSVContainerSpreadSheet.prototype.getContainersByRows = function(rows) { + return _.groupBy(rows, "containerCode"); +}; + +CSVContainerSpreadSheet.prototype.getProteinByAcronym = function(acronym) { + var proteins = EXI.proposalManager.getProteinByAcronym(acronym); + if (proteins){ + if (proteins.length > 0){ + return proteins[0]; + } + } + return null; +}; + +/** +* Returns a set of parcels +* +* @method getParcels +*/ +CSVContainerSpreadSheet.prototype.getParcels = function() { + var _this = this; + function getDiffrationPlanByRow(row){ + return { + radiationSensivity : row["Radiation Sensitivity"], + requiredCompleteness : row["Required Completeness"], + requiredMultiplicity : row["Required multiplicity"], + forcedSpaceGroup : row["forced"], + experimentKind : row["experimentKind"] + + }; + }; + + + + function getCrystalByRow(row){ + return { + spaceGroup : row["Space Group"], + cellA : row["a"], + cellB : row["b"], + cellC : row["c"], + cellAlpha : row["alpha"], + cellBeta : row["beta"], + cellGamma : row["gamma"], + proteinVO : _this.getProteinByAcronym(row["Protein Acronym"]) + }; + }; + + function getSamplesByContainerRows(rows){ + var samples3vo = []; + if (rows){ + for(var i = 0; i< rows.length; i++){ + samples3vo.push({ + name : rows[i]["Sample Name"], + location : rows[i]["position"], + diffractionPlanVO : getDiffrationPlanByRow(rows[i]), + crystalVO : getCrystalByRow(rows[i]), + smiles : rows[i]["Smiles"], + + }); + } + } + return samples3vo; + }; + + function getContainerType(rows){ + if (rows){ + if (rows[0]){ + if (rows[0].containerType){ + return rows[0].containerType; + } + } + } + }; + + function getContainerCapacity(rows){ + if (rows){ + if (rows[0]){ + if (rows[0].containerType){ + if (rows[0].containerType.toUpperCase() == "SPINEPUCK"){ + return 10; + } + return 16; + } + } + } + }; + + var rows = this.parseTableData(); + var dewars3vo = []; + var parcels = this.getParcelsByRows(rows); + for(var parcel in parcels){ + + /** dewars3vo: JSON object with perfect macthing with 3VO ISPyB objects */ + var containerVOs = []; + var containers = this.getContainersByRows(parcels[parcel]); + for (key in containers){ + var containerRows = containers[key]; + containerVOs.push({ + code : _.trim(containerRows[0].containerCode), + containerType : getContainerType(containerRows), + capacity : getContainerCapacity(containerRows), + sampleVOs : getSamplesByContainerRows(containerRows) + }); + } + + dewars3vo.push({ + code : _.trim(parcel), + type : 'Dewar', + containerVOs : containerVOs + }); + } + return dewars3vo; +}; + + +/** +* This method set the property containerNameControlledList as a Set +* +* @method setContainerNameControlledList +* @param {Array} containerNameControlledList list of names of all containers for this shipment +*/ +CSVContainerSpreadSheet.prototype.setContainerNameControlledList = function (containerNameControlledList){ + this.containerNameControlledList = new Set(containerNameControlledList); +}; + + +/** +* This method set the property dewarNameControlledList as a Set +* +* @method setDewarNameControlledList +* @param {Array} dewarNameControlledList list of names of all dewars for this shipment +*/ +CSVContainerSpreadSheet.prototype.setDewarNameControlledList = function (dewarNameControlledList){ + this.dewarNameControlledList = new Set(dewarNameControlledList); +}; + +/** +* Method executed when a change is made on the spreadSheet. It manages the process when the crystal form or the protein acronym are changed +* +* @method manageChange +* @param {Array} change The change made to the spreadSheet as an array of the form [row, column, prevValue, newValue] +* @param {String} source The kind of change. Can be "edit" or "autofill" +* @param {Integer} direction In case of the source being autofill, this parameter indicates the direction of it +*/ +CSVContainerSpreadSheet.prototype.manageChange = function (change, source, direction){ + var rowIndex = change[0]; + var prevValue = change[3]; + + switch (change[1]) { //Column Index + + /** If crystal form has changed */ + case this.crystalFormIndex : { + break; + } + + /** If acronym form has changed */ + case this.getColumnIndex("Protein Acronym") : { + + break; + } + + /** If sample name form has changed */ + case this.getColumnIndex("Sample group") : { + + break; + } + } + + $(".htInvalid").removeClass("htInvalid"); +}; + + +CSVContainerSpreadSheet.prototype.getParcelColor = function(parcelName) { + if (!this.parcelColorBackground[parcelName]){ + this.parcelColorBackground[parcelName] = this.cellColorBackground[_.size(this.parcelColorBackground)%this.cellColorBackground.length]; + /** add parcel name to get the container colors later on */ + this.parcelColorBackground[parcelName].name = parcelName; + } + return this.parcelColorBackground[parcelName].color; +}; + +CSVContainerSpreadSheet.prototype.getContainerColor = function(parcelName, containerName) { + if (this.parcelColorBackground[parcelName]){ + var assignedColorIndex = _.findIndex(_.map(this.parcelColorBackground[parcelName].containers, "name"), function(o){return o == containerName;}) + if (assignedColorIndex == -1){ + for (var i = 0; i < this.parcelColorBackground[parcelName].containers.length; i++){ + if (!this.parcelColorBackground[parcelName].containers[i].name){ + this.parcelColorBackground[parcelName].containers[i].name = containerName; + return this.parcelColorBackground[parcelName].containers[i].color; + } + } + } + else{ + return this.parcelColorBackground[parcelName].containers[assignedColorIndex].color; + } + } + return "#FFFFFF"; +}; + +/** + * Checks the containerType. It checks that it is not empty, not null and it is in the controlled list of values "containerTypeControlledList" + * @method checkContainerType + * @param {String} containerType Type of container read from the CSV file + * @return {Boolean} Returns true if container type is ok or false if container type is not valid + */ +CSVContainerSpreadSheet.prototype.isContainerTypeValid = function(containerType) { + if (containerType){ + if ((containerType != undefined)||(value != "")){ + var foundContainerType = _.find(this.containerTypeControlledList, function(o){return o.name == containerType}); + if (foundContainerType){ + return true; + } + } + } + return false; +}; + + + +/** + * Checks the sample Position. It checks the containerType and checks that position of the sample is < capacity of the dewar + * @method isSamplePositionValid + * @param {String} containerType Type of container read from the CSV file + * @param {String} samplePosition Position of the sample within the container + * @return {Boolean} Returns true if container type is ok or false if container type is not valid + */ +CSVContainerSpreadSheet.prototype.isSamplePositionValid = function(containerType, samplePosition) { + /** Check that container puck is in containerTypeControlledList */ + var containerType = _.find(this.containerTypeControlledList, function(o){return o.name == containerType}); + if (containerType == null){ + return false; + } + + if(containerType == null){ + return false; + } + else{ + try{ + if(containerType.capacity < parseInt(samplePosition)){ + return false; + } + } + catch(e){ + return false; + } + } + return true; +}; + + +/** + * Checks the value. It checks that it is not empty and not null + * @method isValueFilled + * @param {String} containerType Type of container read from the CSV file + * @return {Boolean} Returns true if value is filled + */ +CSVContainerSpreadSheet.prototype.isValueFilled = function(value) { + if ((value == undefined)||(value == "")){ + return false; + } + return true; +}; + +/** + * Checks the name of the parcel. It checks that parcel name does not exist within the shipment. It means in the list of dewarNameControlledList + * @method isParcelNameValid + * @param {String} parcelName Name of the parcel read from CSV + * @return {Boolean} Returns true if name of the parcel is ok + */ +CSVContainerSpreadSheet.prototype.isParcelNameValid = function(parcelName) { + if ((parcelName == undefined)||(parcelName == "")){ + return false; + } + if (this.dewarNameControlledList.has(parcelName)){ + return false; + } + return true; +}; + + +/** + * Checks the name of the sample. (ProteinId + sample name) should be unique for the whole proposal and not empty or null + * @method isSampleNameValid + * @param {String} parcelName Name of the parcel read from CSV + * @return {Boolean} Returns true if name of the parcel is ok + */ +CSVContainerSpreadSheet.prototype.isSampleNameValid = function(sampleName, proteinName) { + if ((sampleName == undefined)||(sampleName == "")){ + return false; + } + var protein = this.getProteinByAcronym(proteinName); + if (protein){ + var conflicts = this.puckValidator.checkSampleNames([sampleName], [protein.proteinId], null, this.proposalSamples); + if (conflicts){ + return false; + } + else{ + return true; + } + } + else{ + return false; + } + +}; + +/** + * Checks the name of the container. + * @method isContainerNameValid + * @param {String} parcelName Name of the parcel read from CSV + * @return {Boolean} Returns true if name of the container is ok + */ +CSVContainerSpreadSheet.prototype.isContainerNameValid = function(containerName) { + if (_this.containerNameControlledList.has(value)){ + return false; + } + return true; +}; + +CSVContainerSpreadSheet.prototype.getHeader = function() { + var _this = this; + var header = []; + + var disabledRenderer = function(instance, td, row, col, prop, value, cellProperties){ + if (value != undefined){ + td.innerHTML = value; + } + td.style.background = '#DDD'; + } + + var mandatoryParameterRenderer = function(instance, td, row, col, prop, value, cellProperties){ + if (!_this.isValueFilled(value)){ + td.className = 'custom-row-text-required'; + } + td.innerHTML = value; + } + + var proteinParameterRenderer = function(instance, td, row, col, prop, value, cellProperties){ + if ((value == undefined)||(value == "")){ + td.className = 'custom-row-text-required'; + return; + } + + var protein = _.find(_this.getAcronyms(), function(o){ return o==value;}); + if (!protein){ + td.className = 'custom-row-text-required'; + } + td.innerHTML = value; + + } + + var sampleParameterRenderer = function(instance, td, row, col, prop, value, cellProperties){ + /** For testing purposes **/ + // value =value + Math.random(); + var proteinName = instance.getSourceDataAtCell(row, _this.PROTEINACRONYM_INDEX); + if (!_this.isSampleNameValid(value, proteinName)){ + td.className = 'custom-row-text-required'; + } + + td.innerHTML = value; + + } + /** Checking parcels name */ + var parcelDisplayCell = function(instance, td, row, col, prop, value, cellProperties){ + if (!_this.isParcelNameValid(value)){ + td.className = 'custom-row-text-required'; + } + else{ + td.style.background = _this.getParcelColor(value); + } + td.innerHTML = value; + } + + /** Checking containers name */ + var containerNameParameterRenderer = function(instance, td, row, col, prop, value, cellProperties){ + if (!_this.isContainerNameValid(value)){ + td.className = 'custom-row-text-required'; + } + else{ + td.style.background = _this.getContainerColor(instance.getDataAtCell(row, col-1), value); + } + td.innerHTML = value; + } + + /** Checking containers type */ + var containerTypeParameterRenderer = function(instance, td, row, col, prop, value, cellProperties){ + if (_this.isContainerTypeValid(value) == false){ + td.className = 'custom-row-text-required'; + } + td.innerHTML = value; + } + + var samplePositionParameterRenderer = function(instance, td, row, col, prop, value, cellProperties){ + var rowContainerType = instance.getSourceDataAtCell(row, _this.CONTAINERTYPE_INDEX); + if (!_this.isSamplePositionValid(rowContainerType, value)){ + td.className = 'custom-row-text-required'; + } + td.innerHTML = value; + } + + + + header = [ + // { text :'', id :'crystalId', column : {width : 100}}, + { text : 'Parcel
Name', id: 'parcel', column : {width : 80, renderer: parcelDisplayCell}}, + { text : 'Container
Name', id: 'containerCode', column : {width : 80, renderer: containerNameParameterRenderer}}, + { text : 'Container
Type', id: 'containerType', column : {width : 80, renderer: containerTypeParameterRenderer}}, + { text : '#', id: 'position', column : {width : 20, renderer: samplePositionParameterRenderer}}, + { text :'Protein
Acronym', id :'Protein Acronym', column : { + width : 80, + type: 'dropdown', + renderer: proteinParameterRenderer, + source: this.getAcronyms() + } + }, + { text :'Sample
Name', id :'Sample Name', column : { + width : 120, + renderer: sampleParameterRenderer + }}, + { text :'Pin
BarCode', id : 'Pin BarCode', column : {width : 60}}, + + + { text :'Exp.
Type', id : 'experimentKind', column : { + width : 100, + type: 'dropdown', + renderer: mandatoryParameterRenderer, + source: [ "Default", "MXPressE", "MXPressO", "MXPressI", "MXPressE_SAD", "MXScore", "MXPressM", "MXPressP", "MXPressP_SAD" ] + } + }, + + { text :'Space
group', id : 'Space Group', column : { + width : 70, + type: 'dropdown', + renderer: mandatoryParameterRenderer, + source: _.concat([""], ExtISPyB.spaceGroups) + }}, + { text :'a', id :'a', column : {width : 40}}, + { text :'b', id :'b', column : {width :40}}, + { text :'c', id :'c', column : {width :40}}, + { text :'α', id :'alpha', column : {width : 40}}, + { text :'β', id :'beta', column : {width : 40}}, + { text :'γ', id :'gamma', column : {width : 40}}, + { text :'Beam
Diameter', id :'Pref. Diameter',column : {width : 60}}, + { text :'Number of
positions', id :'Number Of positions', column : {width : 80}}, + { text :'Radiation
Sensitivity', id :'Radiation Sensitivity', column : {width : 80}}, + + { text :'Required
multiplicity', id :'Required multiplicity', column : {width : 60}}, + { text :'Required
Completeness', id :'Required Completeness', column : {width : 80}}, + { text :'Forced
SPG', id :'forced', column : { + width : 70, + type: 'dropdown', + source: _.concat([""], ExtISPyB.spaceGroups) + }}, + + { text :'SMILES', id :'Smiles', column : {width : 60}}, + { text :'Comments', id :'Comments', column : {width : 200}} + ]; + + + + return header; +};