Scriptrunner post function: Search for highest number in custom fields and set the next higher number on current issue

Kevin
Contributor
March 28, 2017

Dear atlassian community,


I got a custom field (text) named "Invoice Number" containing a number. The field should be filled on a certain workflow transition (post function). I need to get the highest previously used number so far in other issues of the same JIRA project and increase the invoice number of the current issue by 1.

Background: The idea is to generate some sort of sequential invoice number.

That's what I came up with so far. The first part that searches for the highest used invoice number works fine in script console, but when I add this script as a post function I get the following error:

Property 'results' not found
import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.ComponentManager
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.issue.CustomFieldManager
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.IssueManager
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.ModifiedValue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.search.SearchProvider
import com.atlassian.jira.issue.util.DefaultIssueChangeHolder;


def jqlQueryParser = ComponentAccessor.getComponent(JqlQueryParser)
def searchProvider = ComponentAccessor.getComponent(SearchProvider)
def issueManager = ComponentAccessor.getIssueManager()
def user = ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser()

// get all the used invoice numbers so far
def query = jqlQueryParser.parseQuery("project = SJ AND 'Invoice Number' is not EMPTY ORDER BY 'Invoice Number' DESC")
def results = searchProvider.search(query, user, PagerFilter.getUnlimitedFilter())
def i = 0
int newInvoiceNumber = 0

// get the highest used invoice number
results.getIssues().each {
    documentIssue ->
    if (i == 0){
        def issue = issueManager.getIssueObject(documentIssue.id)
        def customFieldManager = ComponentAccessor.getCustomFieldManager()
        CustomField cField = customFieldManager.getCustomFieldObjectByName("Invoice Number")
        int oldInvoiceNumber = issue.getCustomFieldValue(cField) as Integer
        // get the new invoice number
        newInvoiceNumber = ++oldInvoiceNumber
    }
    i++
}


// set the new invoice number
def customFieldManager = ComponentAccessor.getCustomFieldManager()
def tgtField = customFieldManager.getCustomFieldObjects(issue).find {it.name == "Invoice Number"}
DefaultIssueChangeHolder issueChangeHolder = new DefaultIssueChangeHolder();
tgtField.updateValue(null, issue, new ModifiedValue(issue.getCustomFieldValue(tgtField), newInvoiceNumber),issueChangeHolder)

Do you guys have any ideas?
Thank you in advance, and kind regards!



2 answers

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

2 votes
JamieA
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 28, 2017

I've refactored as well, below. I think the above scripts are confusing the issues coming from the query with the issue involved in the transition.

Also you don't need an unlimited pager, if you are sorting by invoice number desc, then you only need the top one.

Note this will only work in a workflow function, not script console, as in script console there is no bound variable "issue".

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.search.SearchProvider
import org.apache.log4j.Logger

MutableIssue issue //predifined variable into a script postfunction

def log = Logger.getLogger("com.springtime.CustomScripts")

// get all the used invoice numbers so far
def query = ComponentAccessor.getComponent(JqlQueryParser).parseQuery("project = SJ AND 'Invoice Number' is not EMPTY ORDER BY 'Invoice Number' DESC")
def results = ComponentAccessor.getComponent(SearchProvider).search(
    query,
    ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser(),
    new PagerFilter(1))

int maxInvoiceNumber = 0
CustomField cField = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Invoice Number")

// get the highest used invoice number
for(Issue documentIssue: results.getIssues()) {
    maxInvoiceNumber = documentIssue.getCustomFieldValue(cField) as Integer
}
// set the new invoice number
issue.setCustomFieldValue(cField, maxInvoiceNumber + 1)
Kevin
Contributor
March 28, 2017

Hi Jamie,

thanks for your input. Just the last part "setCustomFieldValue" doesn't work yet. Seems like the issue is null:

java.lang.NullPointerException: Cannot invoke method setCustomFieldValue() on null object
JamieA
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 28, 2017

You need to remove this line:

MutableIssue issue //predifined variable into a script postfunction


It's masking the actual variable passed to the script, or you can look at it as if it's redefinining it to null.

Kevin
Contributor
March 28, 2017

Hi Jamie, thanks I had to make one final addition. This is the working code now:

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.search.SearchProvider


// get all the used invoice numbers so far
def query = ComponentAccessor.getComponent(JqlQueryParser).parseQuery("project = SJ AND 'Invoice Number' is not EMPTY ORDER BY 'Invoice Number' DESC")
def results = ComponentAccessor.getComponent(SearchProvider).search(query,ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser(),new PagerFilter(1))
int maxInvoiceNumber = 0
CustomField cField = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Invoice Number")


// get the highest used invoice number
for(Issue documentIssue: results.getIssues()) {
    maxInvoiceNumber = documentIssue.getCustomFieldValue(cField) as Integer
}


// set the new invoice number
def newInvoiceNumber = ++maxInvoiceNumber as String
issue.setCustomFieldValue(cField, newInvoiceNumber)
Doug Swartz
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 30, 2017

I believe it's possible for this solution to assign duplicate invoice numbers if for some reason the JIRA indexer is running slowly. JQL queries use the indexed field values to perform the search. If a new invoice issue hasn't been indexed, it will not be returned by the query, allowing a duplicate to be assigned.

Even if the indexer is running normally, it could assign a duplicate invoice numbers if two invoices are needed within milliseconds of each other.

JamieA
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 30, 2017

Have you seen that happen? If so you could use a PropertySet on the current project, that retrieves and updates the counter more or less atomically.

Tarun Sapra
Community Leader
Community Leader
Community Leaders are connectors, ambassadors, and mentors. On the online community, they serve as thought leaders, product experts, and moderators.
March 31, 2017

I also think that this approach doesn't guarantee thread safety, as in there can be race condition. I didn't know about "PropertySet" , would look into it.

Doug Swartz
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 31, 2017

Jamie,

I have seen the indexer be behind enough to causes problems for a plugin using JQL for similar purposes. It doesn't happen often, but should be allowed for. Changing the script to use a PropertySet is safer.

JamieA
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 31, 2017

If you just want a monotonically increasing number, how about just using the issue's id (issue.id) ? Or do you want a number that represents the actual number of invoices?

0 votes
Vasiliy Zverev
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 28, 2017

I refactored your script

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.search.SearchProvider

MutableIssue issue //predifined variable into a script postfunction

def log = Logger.getLogger("com.springtime.CustomScripts")

// get all the used invoice numbers so far
def query = ComponentAccessor.getComponent(JqlQueryParser).parseQuery("project = SJ AND 'Invoice Number' is not EMPTY ORDER BY 'Invoice Number' DESC")
def results = ComponentAccessor.getComponent(SearchProvider).search(
        query,
        ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser(), 
        PagerFilter.getUnlimitedFilter())

int maxInvoiceNumber = 0
int curInvoiceNumber;
CustomField cField = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Invoice Number")

// get the highest used invoice number
for(Issue documentIssue: results.getIssues()) {
    curInvoiceNumber = issue.getCustomFieldValue(cField)
    if(curInvoiceNumber > maxInvoiceNumber)
        maxInvoiceNumber = curInvoiceNumber
}
// set the new invoice number
issue.setCustomFieldValue(cField, maxInvoiceNumber + 1)
Kevin
Contributor
March 28, 2017

Hello, thank you for your effort.

The mentioned error is now gone, but another error occurs:

java.lang.NullPointerException: Cannot invoke method getCustomFieldValue() on null object

Further the new number is not added to the custom field.

Vasiliy Zverev
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
March 29, 2017

Here you are

import com.atlassian.jira.component.ComponentAccessor
import com.atlassian.jira.jql.parser.JqlQueryParser
import com.atlassian.jira.web.bean.PagerFilter
import com.atlassian.jira.issue.fields.CustomField
import com.atlassian.jira.issue.Issue
import com.atlassian.jira.issue.MutableIssue
import com.atlassian.jira.issue.search.SearchProvider
 
MutableIssue issue //predifined variable into a script postfunction
 
def log = Logger.getLogger("com.springtime.CustomScripts")
 
// get all the used invoice numbers so far
def query = ComponentAccessor.getComponent(JqlQueryParser).parseQuery("project = SJ AND 'Invoice Number' is not EMPTY ORDER BY 'Invoice Number' DESC")
def results = ComponentAccessor.getComponent(SearchProvider).search(
                query,
                ComponentAccessor.getJiraAuthenticationContext().getLoggedInUser(),
                PagerFilter.getUnlimitedFilter())
 
int maxInvoiceNumber = 0
int curInvoiceNumber;
CustomField cField = ComponentAccessor.getCustomFieldManager().getCustomFieldObjectByName("Invoice Number")
 
// get the highest used invoice number
for(Issue documentIssue: results.getIssues()) {
        curInvoiceNumber = documentIssue.getCustomFieldValue(cField)
        if(curInvoiceNumber > maxInvoiceNumber)
            maxInvoiceNumber = curInvoiceNumber
}
// set the new invoice number
issue.setCustomFieldValue(cField, maxInvoiceNumber + 1)
Like Florian Reichl likes this
TAGS
AUG Leaders

Atlassian Community Events