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;
+};