NetSuite UserEventScript: TypeError: Cannot find function getSubListValue in object standard record - suitescript2.0

I am creating a UserEvent script in NetSuite, but I am not able to get data from the Items sublist. I'm using the getSubListValue method for this. Could someone give me a hand? I'm posting the code. Thank you!
getSublistText, getSublistField functions
/**
* #NApiVersion 2.x
* #NScriptType userEventScript
* #author Adriano Barbosa
* #since 2019.2
*/
define(['N/log', 'N/record', 'N/search', 'N/ui/dialog', 'N/ui/serverWidget'], function(log, record, search, dialog, ui) {
function devolucaoFornecedor(context) {
var df, total_linhas_itens, codigo_item_linha, qtde_item_linha, peso_item, peso_bruto_liquido;
df = context.newRecord;
total_linhas_itens = df.getLineCount({ sublistId: 'item' });
log.debug({ title: 'total_linhas_itens', details: total_linhas_itens });
if ( context.type === context.UserEventType.CREATE ) {
for ( i=0; i<total_linhas_itens; i++ ) {
// codigo_item_linha = df.getSubListValue({ sublistId: 'item', fieldId: 'item', line: linha_item });
codigo_item_linha = df.getSubListValue({ sublistId: 'item', fieldId: 'item', line: i });
log.debug({ title: 'codigo_item_linha', details: codigo_item_linha });
qtde_item_linha = df.getSubListValue({ subslitId: 'item', field: 'quantity', line: i });
log.debug({ title: 'qtde_item_linha', details: qtde_item_linha });
peso_item = record.load({ type: 'inventoryitem', id: codigo_item_linha, isDynamic: true })
.getValue({ fieldId: 'custitem_enl_weight' });
log.debug({ title: 'peso_item', details: peso_item });
volumes += qtde_item_linha;
peso_bruto_liquido += ( peso_item * qtde_item_linha );
log.debug({ title: 'volumes', details: volumes });
log.debug({ title: 'peso_bruto_liquido', details: peso_bruto_liquido });
}
df.setValue({ fieldId: 'custbody_enl_grossweight', value: peso_bruto_liquido });
df.setValue({ fieldId: 'custbody_enl_netweight', value: peso_bruto_liquido });
df.setValue({ fieldId: 'custbody_enl_volumesqty', value: volumes });
df.setValue({ fieldId: 'custbody_enl_volumetype', value: 'PC' });
}
return true;
}
return {
afterSubmit: devolucaoFornecedor
}
})
TypeError: Can not find function getSubListValue in object standard record
TypeError: Can not find function getSubLisText in object standard record

I think it is just typo.
getSubListValue(options) --> getSublistValue(options)

With the help of friend Kenji I'm posting the updated script. I hope it can be useful to everyone. Thank you!
/**
* #NApiVersion 2.x
* #NScriptType userEventScript
* #author Adriano Barbosa
* #since 2019.2
*/
define(['N/log', 'N/record', 'N/search'], function(log, record, search) {
function devolucaoFornecedor(context) {
var df, itemCount, id_item_linha, bsc_id_item_linha, qtde_item_linha, peso_item, peso_bl, volumes,
peso_total_itens, fornecedor;
var bodyObject = {};
var itens = [];
df = context.newRecord;
itemCount = df.getLineCount({ sublistId: 'item' });
volumes = 0;
peso_total_itens = 0;
for ( linha_item=0; linha_item<itemCount; linha_item++ ) {
id_item_linha = df.getSublistValue({ sublistId: 'item', fieldId: 'item', line: linha_item });
bsc_id_item_linha = search.lookupFields({ type: 'inventoryitem', id: id_item_linha,
columns: [ 'itemid', 'custitem_enl_weight' ]
});
qtde_item_linha = df.getSublistValue({ sublistId: 'item', fieldId: 'quantity', line: linha_item });
peso_item = bsc_id_item_linha.custitem_enl_weight;
// peso_bl = (peso_item x qtde_item_linha)
peso_bl = peso_item * qtde_item_linha;
// volumes = quantidade de todos os itens da DF
volumes = qtde_item_linha + volumes;
peso_total_itens = peso_bl + peso_total_itens;
var itensObj = {};
itensObj['codigo_item_linha'] = bsc_id_item_linha.itemid;
itensObj['qtde_item_linha'] = qtde_item_linha;
itensObj['peso_item'] = peso_item || 0;
itensObj['peso_bl'] = peso_bl || 0;
itensObj['volumes'] = volumes;
itensObj['peso_total_itens'] = peso_total_itens;
itens.push(itensObj);
}
if ( context.type === context.UserEventType.CREATE ) {
bodyObject['total_linhas_itens'] = itemCount;
bodyObject['peso_geral'] = peso_total_itens;
bodyObject['volume_geral'] = volumes;
bodyObject['itens'] = itens;
df.setValue({ fieldId: 'custbody_enl_grossweight', value: peso_total_itens })
.setValue({ fieldId: 'custbody_enl_netweight', value: peso_total_itens })
.setValue({ fieldId: 'custbody_enl_volumesqty', value: volumes })
.setValue({ fieldId: 'custbody_enl_volumetype', value: 'PC' });
log.debug({ title: 'Devolução de Fornecedor cadastrada!', details: bodyObject });
return true;
} else if ( context.type === context.UserEventType.EDIT ) {
bodyObject['id_df'] = context.newRecord.id;
bodyObject['num_df'] = df.getValue({ fieldId: 'tranid' });
bsc_fornecedor_id_df = search.create({ type: "vendorreturnauthorization",
filters: [
[ "type", "anyof" , "VendAuth" ], "AND",
[ "internalid", "anyof" , bodyObject['id_df'] ], "AND",
["mainline","is","T"]
],
columns: [
search.createColumn({ name: "formulatext", formula: "{entity}", label: "fornecedor" })
]
}).run().each(function(result){
fornecedor = result.getValue({ name: 'formulatext' });
log.debug({ title: 'fornecedor', details: fornecedor });
return false;
});
bodyObject['fornecedor'] = fornecedor || 'Vide num_df';
bodyObject['total_linhas_itens'] = itemCount;
bodyObject['peso_geral'] = peso_total_itens;
bodyObject['volume_geral'] = volumes;
bodyObject['itens'] = itens;
df.setValue({ fieldId: 'custbody_enl_grossweight', value: peso_total_itens })
.setValue({ fieldId: 'custbody_enl_netweight', value: peso_total_itens })
.setValue({ fieldId: 'custbody_enl_volumesqty', value: volumes })
.setValue({ fieldId: 'custbody_enl_volumetype', value: 'PC' });
log.debug({ title: 'Devolução de Fornecedor Modificada', details: bodyObject });
return true;
}
return true;
}
return { beforeSubmit: devolucaoFornecedor }
})

Related

Suitescript 2.0-Why is a new line inserted into a sublist on record edit

I have the following client script deployed onto a vendor bill
/**
*#NApiVersion 2.x
*#NScriptType ClientScript
*/
define(["N/record"], function (record) {
/**
* #param {ClientScriptContext.fieldChanged} context
*/
function fieldChanged(context) {
//the if condition ensures the field changed trigger does not execute on changes in sublist. setting this to
//!context.sublistid ensures changes in department field and location field for the sublist do not trigger the script
if (
!context.sublistId &&
(context.fieldId == "department" || context.fieldId == "location")
) {
log.debug("sublistId", context.sublistId)
log.debug("fieldId", context.fieldId)
var recordObj = context.currentRecord
var departmentMain = recordObj.getValue({ fieldId: "department" })
var locationMain = recordObj.getValue({ fieldId: "location" })
var expenseSublist = recordObj.getSublist({ sublistId: "expense" })
var expenseSublistLength = recordObj.getLineCount({
sublistId: "expense",
})
log.debug("department", departmentMain)
log.debug("location", locationMain)
log.debug("expenseSublist.length", expenseSublistLength)
for (var i = 0; i < expenseSublistLength; i++) {
recordObj.selectLine({
sublistId: "expense",
line: i,
})
var departmentLine = recordObj.setCurrentSublistValue({
sublistId: "expense",
fieldId: "department",
value: departmentMain,
ignoreFieldChange: true,
})
var locationLine = recordObj.setCurrentSublistValue({
sublistId: "expense",
fieldId: "location",
value: locationMain,
ignoreFieldChange: true,
})
recordObj.commitLine({ sublistId: "expense" })
log.debug("department", departmentLine)
log.debug("location", locationLine)
}
}
}
/**
* #param {ClientScriptContext.postSourcing} context
*/
function postSourcing(context) {
var currentRecord = context.currentRecord
var sublistName = context.sublistId
var sublistFieldName = context.fieldId
var line = context.line
var departmentMain = currentRecord.getValue({ fieldId: "department" })
var locationMain = currentRecord.getValue({ fieldId: "location" })
if (sublistName === "expense" && sublistFieldName === "account") {
var expenseSublist = currentRecord.getSublist({ sublistId: "expense" })
var expenseSublistLength = currentRecord.getLineCount({
sublistId: "expense",
})
log.debug("department-main", departmentMain)
log.debug("location-main", locationMain)
currentRecord.setCurrentSublistValue({
sublistId: "expense",
fieldId: "department",
value: departmentMain,
ignoreFieldChange: false,
})
currentRecord.setCurrentSublistValue({
sublistId: "expense",
fieldId: "location",
value: locationMain,
ignoreFieldChange: false,
})
}
}
return {
fieldChanged: fieldChanged,
postSourcing: postSourcing,
}
})
When I edit an existing vendor bill record and attempt to save, I get the following prompt
It looks like placing the record into edit mode has also inserted a new blank line into the existing sublist despite no fields being clicked
Why would that be the case? Based on the above script, my expectation is that the postsourcing and field changed functions should only occur if the relevant fields (department and location for field changed and account for post sourcing) are clicked.
Is there something my script is missing?

Add quantity for expense sublist on line commit

I have an expense report transaction where I am trying to sum the quantity field on the sublist and update a custom field (i.e. basically a running total that is updated every time a line is committed)
This is a part of the client script that I have so far:
function sublistChanged(context) {
var currentRecord = context.currentRecord;
var sublistName = context.sublistId;
var sublistFieldName = context.fieldId;
var op = context.operation;
totalExpQuantity = 0;
if (sublistName === "expense") {
for (var i = 0; i < sublistName.length; i++) {
var quantity = currentRecord.getCurrentSublistValue({
sublistId: "expense",
fieldId: "quantity",
});
log.debug("quantity", quantity);
}
totalExpQuantity += parseInt(quantity);
var lineCount = currentRecord.getLineCount({
sublistId: "expense",
});
console.log(lineCount);
currentRecord.setValue({
fieldId: "custbody_mileage_exp_report",
value:
"Total has changed to " +
currentRecord.getValue({
fieldId: "amount",
}) +
" with operation: " +
op +
" total quantity: " +
totalExpQuantity,
});
}
}
Every time a line is committed with a quantity value, I want the total to increment by the value of the line that was committed
i.e. here, after every line is committed, the total should go from 10,000, to 10,500 to 15,500
I have tried a variation of this code where the following line is part of the 'for loop':
totalExpQuantity += parseInt(quantity);
i.e.
for (var i = 0; i < sublistName.length; i++) {
var quantity = currentRecord.getCurrentSublistValue({
sublistId: "expense",
fieldId: "quantity",
});
totalExpQuantity += parseInt(quantity);
log.debug("quantity", quantity);
}
I get the following result:
Is what I am trying to do possible?
Where am I going wrong in the code? Should it be a different entry point?
I have also tried postsourcing though it didn't retrieve the quantity field and returned a blank
Found the answer. This is the working version of the script:
/**
*#NApiVersion 2.x
*#NScriptType ClientScript
*/
define(["N/search"], function (search) {
function fieldChanged(context) {
try {
var recordObj = context.currentRecord;
var employeeObj = parseInt(
recordObj.getValue({
fieldId: "entity",
})
);
if (context.fieldId == "entity") {
var employeeName = parseInt(employeeObj);
log.debug("employee", employeeName);
var expensereportSearchObj = search.create({
type: "expensereport",
filters: [
["type", "anyof", "ExpRept"],
"AND",
["expensecategory", "anyof", "8"],
"AND",
["expensedate", "within", "thisfiscalyear"],
"AND",
["employee", "anyof", employeeObj],
],
columns: [
search.createColumn({
name: "entityid",
join: "employee",
label: "Name",
}),
search.createColumn({ name: "tranid", label: "Document Number" }),
search.createColumn({
name: "expensecategory",
label: "Expense Category",
}),
search.createColumn({ name: "expensedate", label: "Expense Date" }),
search.createColumn({ name: "currency", label: "Currency" }),
search.createColumn({
name: "quantity",
join: "expenseDetail",
sort: search.Sort.ASC,
label: "Quantity",
}),
],
});
var searchResult = expensereportSearchObj
.run()
.getRange({ start: 0, end: 1000 });
log.debug("result", JSON.stringify(searchResult));
var searchResultCount = expensereportSearchObj.runPaged().count;
log.debug("expensereportSearchObj result count", searchResultCount);
let q = 0;
for (var i = 0; i < searchResult.length; i++) {
var quantity = searchResult[i].getValue(searchResult[i].columns[5]);
log.debug("quantity", quantity);
q += parseInt(quantity);
log.debug("q", q);
}
recordObj.setValue({
fieldId: "custbody_employee_mileage_ytd",
value: q,
});
}
//loop through all results add +- to 0. see video for sub;ists
} catch (error) {
log.debug(
error.name,
"recordObjId: " +
recordObj +
", employee:" +
employeeName +
", message: " +
error.message +
", cause: " +
error.cause
);
}
}
function sublistChanged(context) {
var recordObj = context.currentRecord;
var sublistName = recordObj.getSublist({ sublistId: "expense" });
var lineCount = recordObj.getLineCount({ sublistId: "expense" });
var totalQuantity = 0;
for (i = 0; i < lineCount; i++) {
var expenseCategory = recordObj.getSublistValue({
sublistId: "expense",
fieldId: "category",
line: i,
});
log.debug("expenseCategory", expenseCategory);
var expenseQuantity = recordObj.getSublistValue({
sublistId: "expense",
fieldId: "quantity",
line: i,
});
if (expenseCategory == 8) {
totalQuantity += expenseQuantity;
}
recordObj.setValue({
fieldId: "custbody_mileage_exp_report",
value: totalQuantity,
});
}
}
return {
fieldChanged: fieldChanged,
sublistChanged: sublistChanged,
};
});

Can't pass my data to the mounted() method

I am fairly new to Vue so excuse my stupidity!!
Can anyone tell me what I am doing wrong in my code below that is preventing me from getting my 'series_interactions_daily' variable data from inside the mounted() method please? I am console.log()'ing it but nothing shows! I can print 'series_interactions_daily' to screen in the HTML though!
<script>
import * as am4core from "#amcharts/amcharts4/core";
import * as am4charts from "#amcharts/amcharts4/charts";
import am4themes_animated from "#amcharts/amcharts4/themes/animated";
am4core.useTheme(am4themes_animated);
export default {
mounted() {
let chart = am4core.create(this.$refs.chartdiv, am4charts.XYChart);
chart.paddingRight = 20;
let dummydata = [];
let visits = 10;
for (let i = 1; i < 366; i++) {
visits += Math.round((Math.random() < 0.5 ? 1 : -1) * Math.random() * 10);
dummydata.push({ date: new Date(2018, 0, i), name: "name" + i, value: visits });
}
chart.data = dummydata;
console.log(this.series_interactions_daily);
let dateAxis = chart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.grid.template.location = 0;
let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.tooltip.disabled = true;
valueAxis.renderer.minWidth = 35;
let series = chart.series.push(new am4charts.LineSeries());
series.dataFields.dateX = "date";
series.dataFields.valueY = "value";
series.tooltipText = "{valueY.value}";
chart.cursor = new am4charts.XYCursor();
let scrollbarX = new am4charts.XYChartScrollbar();
scrollbarX.series.push(series);
chart.scrollbarX = scrollbarX;
this.chart = chart;
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose();
}
},
data() {
return {
campaign_id: this.$route.params.campaign_id,
campaign: [],
dateranges: [],
total_interactions: '',
average_daily_interactions: '',
series_interactions_daily: [],
}
},
methods: {
onChange(event) {
this.$router.push('?range=' + event.target.value);
this.$http.get('https://dev.local/api/portal/campaign/' + this.campaign_id + '?range=' + event.target.value)
.then(function(data){
return data.json();
}).then(function(data){
this.campaign = data.campaign;
this.dateranges = data.dateranges;
this.total_interactions = data.totalInteractions;
this.average_daily_interactions = data.averagreDailyInteractions;
this.series_interactions_daily = data.interactions_per_day.stats.interaction.daily;
});
}
},
created() {
this.$http.get('https://dev.local/api/portal/campaign/' + this.campaign_id + '?' + this.axiosParams)
.then(function(data){
return data.json();
}).then(function(data){
this.campaign = data.campaign;
this.dateranges = data.dateranges;
this.total_interactions = data.totalInteractions;
this.average_daily_interactions = data.averagreDailyInteractions;
this.series_interactions_daily = data.interactions_per_day.stats.interaction.daily;
});
},
computed: {
axiosParams() {
const params = new URLSearchParams();
if(this.$route.query.range) params.append('range', this.$route.query.range);
return params;
}
},
filters: {
},
directives: {
},
}
</script>
EDIT
OK, so I have create a method to fetchData() from mounted(){} - however I am still not getting my 'series_interactions_daily' variable data!
<script>
import * as am4core from "#amcharts/amcharts4/core";
import * as am4charts from "#amcharts/amcharts4/charts";
import am4themes_animated from "#amcharts/amcharts4/themes/animated";
am4core.useTheme(am4themes_animated);
export default {
mounted() {
this.fetchData();
let chart = am4core.create(this.$refs.chartdiv, am4charts.XYChart);
chart.paddingRight = 20;
let dummydata = [];
let visits = 10;
for (let i = 1; i < 366; i++) {
visits += Math.round((Math.random() < 0.5 ? 1 : -1) * Math.random() * 10);
dummydata.push({ date: new Date(2018, 0, i), name: "name" + i, value: visits });
}
chart.data = dummydata;
console.log(this.series_interactions_daily);
let dateAxis = chart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.grid.template.location = 0;
let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.tooltip.disabled = true;
valueAxis.renderer.minWidth = 35;
let series = chart.series.push(new am4charts.LineSeries());
series.dataFields.dateX = "date";
series.dataFields.valueY = "value";
series.tooltipText = "{valueY.value}";
chart.cursor = new am4charts.XYCursor();
let scrollbarX = new am4charts.XYChartScrollbar();
scrollbarX.series.push(series);
chart.scrollbarX = scrollbarX;
this.chart = chart;
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose();
}
},
data() {
return {
campaign_id: this.$route.params.campaign_id,
campaign: [],
dateranges: [],
total_interactions: '',
average_daily_interactions: '',
series_interactions_daily: [],
}
},
methods: {
fetchData() {
this.$http.get('https://dev.local/api/portal/campaign/' + this.campaign_id + '?' + this.axiosParams)
.then(function(data){
return data.json();
}).then(function(data){
this.campaign = data.campaign;
this.dateranges = data.dateranges;
this.total_interactions = data.totalInteractions;
this.average_daily_interactions = data.averagreDailyInteractions;
this.series_interactions_daily = data.interactions_per_day.stats.interaction.daily;
});
},
onChange(event) {
this.$router.push('?range=' + event.target.value);
this.$http.get('https://connie.laravel/api/portal/campaign/' + this.campaign_id + '?range=' + event.target.value)
.then(function(data){
return data.json();
}).then(function(data){
this.campaign = data.campaign;
this.dateranges = data.dateranges;
this.total_interactions = data.totalInteractions;
this.average_daily_interactions = data.averagreDailyInteractions;
this.series_interactions_daily = data.interactions_per_day.stats.interaction.daily;
});
}
},
created() {
},
computed: {
axiosParams() {
const params = new URLSearchParams();
if(this.$route.query.range) params.append('range', this.$route.query.range);
return params;
}
},
filters: {
},
directives: {
},
}
</script>
Any help appreciated.
Thanks,
K...
In fetchData, return the promise returned by the async $http call:
fetchData() {
return this.$http.get(...)
...
}
Now you can register a callback on that promise in mounted:
mounted() {
this.fetchData().then(result => {
...
});
}
Or just await it with async/await:
async mounted() {
await this.fetchData();
...
// Now everything will be ready
}
As #tao mentioned in comments, note that the await would delay any code that comes after it. If you don't have anything else to do in the hook anyway that's no problem but it's something to understand.

How to join two collections in a json store?

I am working on IBM Worklight. I need to access data from two collections. Is there any solution for joining the 2 collections and get the required fields ?
JSONSTORE does not have the ability to combine collections.
However the follow blog post details one way to achieve this: https://mobilefirstplatform.ibmcloud.com/blog/2015/02/24/working-jsonstore-collections-join/
Create a collection:
var OrdersCollection = {
orders: {
searchFields: {
order_id: 'integer',
order_date: 'string'
},
additionalSearchFields: {
customer_id: 'integer'
}
}
};
var CustomerCollection = {
customers: {
searchFields: {
customer_name : 'string',
contact_name : 'string',
country : 'string'
},
additionalSearchFields : {
customer_id : 'integer'
}
}
};
Add data using additional search fields:
var data = [
{order_id : 462, order_date : '1-1-2000'},
{order_id: 608, order_date : '2-2-2001'},
{order_id: 898, order_date : '3-3-2002'}
];
var counter = 0;
var q = async.queue(function (task, callback) {
setTimeout(function () {
WL.JSONStore.get('orders').add(task.data, {additionalSearchFields: {customer_id: task.customer_id}});
callback(++counter);
},0);
},1);
for(var i = 0; i < data.length; i++){
q.push({data : data[i], customer_id: i+1}, function(counter){
console.log("Added Order Doc " + counter);
});
}
q.drain = function(){
setTimeout(function() {
console.log("Finished adding order documents");
WL.JSONStore.get("orders").findAll({filter : ["customer_id", "order_id", "order_date"]})
.then(function (res) {
ordersCollectionData = res;
document.getElementById("orders").innerHTML = JSON.stringify(res, null, "\t");
})
.fail(function (err) {
console.log("There was a problem at " + JSON.stringify(err));
});
},0);
};
Find the documents to merge:
WL.JSONStore.get('orders').findAll({filter : ['customer_id', 'order_id', 'order_date']})
.then(function (res) {
ordersCollectionData = res;
});
Merge
var shared_index = 'customer_id';
var mergedCollection = [];
mergedCollection =_.merge(customerCollectionData, ordersCollectionData, function(a, b){
if(_.isObject(a) && (a[shared_index] == b[shared_index])) {
return _.merge({}, a, b);
}
});

On my cardboard app trying to show total of all features actual points for each column(Release) on the header, but that is not getting displayed

On my cardboard app trying to show total of all features actual points for each column(Release) on the header, but that is not getting displayed.
See the image below the cards are my portfolioitem/feature, in particular release.
On each header with release information, below progress-bar I want to show total of all features actual points in that release.
Below is the code for that, I am not getting why actual_points are not getting displayed. I am doing something wrong, but I don't know where exactly it is
Ext.override(Rally.ui.cardboard.CardBoard,{
_buildColumnsFromModel: function() {
var me = this;
var model = this.models[0];
if (model) {
if ( this.attribute === "Release" ) {
var retrievedColumns = [];
retrievedColumns.push({
value: null,
columnHeaderConfig: {
headerTpl: "{name}",
headerData: {
name: "Backlog"
}
}
});
this._getLocalReleases(retrievedColumns, this.globalVar);
}
}
},
_getLocalReleases: function(retrievedColumns, today_iso) {
var me = this;
if (today_iso == undefined) {
today_iso = Rally.util.DateTime.toIsoString(new Date(),false);
}
var filters = [{property:'ReleaseDate',operator:'>',value:today_iso}];
var iteration_names = [];
Ext.create('Rally.data.WsapiDataStore',{
model:me.attribute,
autoLoad: true,
filters: filters,
context: { projectScopeUp: false, projectScopeDown: false },
sorters: [
{
property: 'ReleaseDate',
direction: 'ASC'
}
],
//limit: Infinity,
pageSize: 4,
//buffered: true,
//purgePageCount: 4,
fetch: ['Name','ReleaseStartDate','ReleaseDate','PlannedVelocity'],
listeners: {
load: function(store,records) {
Ext.Array.each(records, function(record){
console.log("records values", records);
var start_date = Rally.util.DateTime.formatWithNoYearWithDefault(record.get('ReleaseStartDate'));
var end_date = Rally.util.DateTime.formatWithNoYearWithDefault(record.get('ReleaseDate'));
//this._getMileStones(record.get('ReleaseStartDate'), record.get('ReleaseDate'), this.project);
iteration_names.push(record.get('Name'));
//iteration_names.push(record.get('ReleaseDate'));
retrievedColumns.push({
value: record,
_planned_velocity: 0,
_actual_points: 0,
_missing_estimate: false,
columnHeaderConfig: {
headerTpl: "{name}<br/>{start_date} - {end_date}",
headerData: {
name: record.get('Name'),
start_date: start_date,
end_date: end_date,
planned_velocity: 0,
actual_points: 0,
missing_estimate: false
}
}
});
});
this._getAllReleases(retrievedColumns,iteration_names);
},
scope: this
}
});
},
_getAllReleases: function(retrievedColumns,iteration_names, today_iso) {
var me = this;
if (today_iso == undefined) {
today_iso = Rally.util.DateTime.toIsoString(new Date(),false);
}
var filters = [{property:'ReleaseDate',operator:'>',value:today_iso}];
Ext.create('Rally.data.WsapiDataStore',{
model:me.attribute,
autoLoad: true,
filters: filters,
sorters: [
{
property: 'ReleaseDate',
direction: 'ASC'
}
],
fetch: ['Name','Project','PlannedVelocity'],
listeners: {
load: function(store,records) {
Ext.Array.each(records, function(record){
var planned_velocity = record.get('PlannedVelocity') || 0;
var actual_points = record.get('LeafStoryPlanEstimateTotal') || 0;
var index = Ext.Array.indexOf(iteration_names[0],record.get('Name'));
if (planned_velocity == 0 ) {
retrievedColumns[index+1]._missing_estimate = true;
}
retrievedColumns[index+1]._actual_points += actual_points;
retrievedColumns[index+1]._planned_velocity += planned_velocity;
});
this.fireEvent('columnsretrieved',this,retrievedColumns);
this.columnDefinitions = [];
_.map(retrievedColumns,this.addColumn,this);
this._renderColumns();
},
scope: this
}
});
}
});
Ext.define('Rally.technicalservices.plugin.ColumnHeaderUpdater', {
alias: 'plugin.tscolumnheaderupdater',
extend: 'Ext.AbstractPlugin',
config: {
/**
*
* #type {String} The name of the field holding the card's estimate
*
* Defaults to c_FeatureEstimate (try LeafStoryPlanEstimateTotal)
*/
field_to_aggregate: "planned_velocity",
/**
* #property {Number} The current count of feature estimates
*/
total_feature_estimate: 0,
fieldToDisplay: "actual_points",
/**
* #property {String|Ext.XTemplate} the header template to use
*/
headerTpl: new Rally.technicalservices.template.LabeledProgressBarTemplate({
fieldLabel: 'Features Planned vs Planned Velocity: ',
calculateColorFn: function(data) {
if ( data.percentDone > 0.9 ) {
return '#EDB5B1';
}
return '#99CCFF';
},
showDangerNotificationFn: function(data) {
return data.missing_estimate;
},
generateLabelTextFn: function(data) {
if ( data.percentDone === -1 ) {
return "No Planned Velocity";
} else {
var text_string = "";
if ( data.field_to_aggregate === "planned_velocity" ) {
text_string = this.calculatePercent(data) + '%';
} else {
text_string = 'By Story: ' + this.calculatePercent(data) + '%';
}
return text_string;
}
}
})
//headerTpl: '<div class="wipLimit">({total_feature_estimate} of {planned_velocity})</div>'
},
constructor: function(config) {
this.callParent(arguments);
if(Ext.isString(this.headerTpl)) {
this.headerTpl = Ext.create('Ext.XTemplate', this.headerTpl);
}
},
init: function(column) {
this.column = column;
if ( column.value === null ) {
this.headerTpl = new Ext.XTemplate('');
}
this.planned_velocity = this.column._planned_velocity;
this.missing_estimate = this.column._missing_estimate;
this.actual_points = this.column._actual_points;
this.column.on('addcard', this.recalculate, this);
this.column.on('removecard', this.recalculate, this);
this.column.on('storeload', this.recalculate, this);
this.column.on('afterrender', this._afterRender, this);
this.column.on('ready', this.recalculate, this);
this.column.on('datachanged', this.recalculate, this);
},
destroy: function() {
if(this.column) {
delete this.column;
}
},
_afterRender: function() {
if ( this.feature_estimate_container ) {
this.feature_estimate_container.getEl().on('click', this._showPopover, this);
}
},
recalculate: function() {
this.total_feature_estimate = this.getTotalFeatureEstimate();
this.refresh();
},
refresh: function() {
var me = this;
if (this.feature_estimate_container) {
this.feature_estimate_container.update(this.headerTpl.apply(this.getHeaderData()));
} else {
this.feature_estimate_container = Ext.widget({
xtype: 'container',
html: this.headerTpl.apply(this.getHeaderData())
});
this.column.getColumnHeader().getHeaderTitle().add(this.feature_estimate_container);
}
if ( this.feature_estimate_container && this.feature_estimate_container.getEl()) {
this.feature_estimate_container.getEl().on('click', this._showPopover, this);
}
},
_showPopover: function() {
var me = this;
if ( me.planned_velocity > 0 ) {
if ( this.popover ) { this.popover.destroy(); }
this.popover = Ext.create('Rally.ui.popover.Popover',{
target: me.column.getColumnHeader().getHeaderTitle().getEl(),
items: [ me.getSummaryGrid() ]
});
this.popover.show();
}
},
getSummaryGrid: function() {
var me = this;
var estimate_title = "Feature Estimates";
if ( this.field_to_aggregate !== "c_FeatureEstimate") {
estimate_title = "Story Estimates";
}
var store = Ext.create('Rally.data.custom.Store',{
data: [
{
'PlannedVelocity': me.planned_velocity,
'ActualPoints': me.actual_points,
'TotalEstimate': me.getTotalFeatureEstimate(),
'Remaining': me.getCapacity(),
'MissingEstimate': me.missing_estimate
}
]
});
var grid = Ext.create('Rally.ui.grid.Grid',{
store: store,
columnCfgs: [
{ text: 'Plan', dataIndex:'PlannedVelocity' },
{ text: estimate_title, dataIndex: 'TotalEstimate' },
{ text: 'Remaining', dataIndex: 'Remaining' },
{ text: 'Team Missing Plan?', dataIndex: 'MissingEstimate' }
],
showPagingToolbar: false
});
return grid;
},
getHeaderData: function() {
var total_feature_estimate = this.getTotalFeatureEstimate();
actual_points = 0;
var percent_done = -1;
planned_velocity = 20;
if ( planned_velocity > 0 ) {
percent_done = total_feature_estimate / 4;
}
return {
actual_points: actual_points,
total_feature_estimate: total_feature_estimate,
planned_velocity: planned_velocity,
percentDone: percent_done,
field_to_aggregate: this.field_to_aggregate,
missing_estimate: this.missing_estimate
};
},
getCapacity: function() {
return this.planned_velocity - this.getTotalFeatureEstimate();
},
getTotalFeatureEstimate: function() {
var me = this;
var total = 0;
var total_unaligned = 0;
var records = this.column.getRecords();
Ext.Array.each(records, function(record){
var total_points = record.get('AcceptedLeafStoryPlanEstimateTotal');
var feature_estimate = record.get(me.field_to_aggregate) || 0;
var unaligned_estimate = record.get('UnalignedStoriesPlanEstimateTotal') || 0;
total += parseFloat(total_points,10);
});
if ( me.field_to_aggregate !== "planned_velocity" ) {
total = total
}
return total;
}
});