xquery version "3.0";
(:
    ART-DECOR® STANDARD COPYRIGHT AND LICENSE NOTE
    Copyright © ART-DECOR Expert Group and ART-DECOR Open Tools GmbH
    see https://docs.art-decor.org/copyright and https://docs.art-decor.org/licenses

    This file is part of the ART-DECOR® tools suite.
:)

(:
   Xquery for setting statusCode of decor object
   Input: post of statusChange element:
   <statusChange ref="2.16.840.1.113883.3.1937.99.62.3.11.6" effectiveDate="2013-09-24T14:32:15" statusCode="final" versionLabel="Test" expirationDate="2013-10-31" actionText="Status op 'Definitief' zetten"/>
:)
import module namespace get             = "http://art-decor.org/ns/art-decor-settings" at "art-decor-settings.xqm";
import module namespace art             = "http://art-decor.org/ns/art" at "art-decor.xqm";
import module namespace decor           = "http://art-decor.org/ns/decor" at "../api/api-decor.xqm";
import module namespace templ           = "http://art-decor.org/ns/decor/template" at "../api/api-decor-template.xqm";
import module namespace hist            = "http://art-decor.org/ns/decor/history" at "../api/api-decor-history.xqm";

(:
:   The UI has dealt with whether or not this status transition is allowable, but will check here too.
:   We get these properties to set:
:   <statusChange ref="object-id" effectiveDate="object-effectiveDate?" statusCode="xs:string" versionLabel="xs:sting?" expirationDate="xs:dateTime?" recurse="true|false"/>
:
:   Step 1: find object
:           if not: <response>NOT FOUND</response>
:   Step 2: check if user is author in object project
:           if not: <response>NO PERMISSION</response>
:   Step 3: check if object has lock set and if @recurse='true' check all underlying objects
:           recursion if only applicable to dataset|concept|scenario|transaction
:           if lock: <response>{first-lock}</response>
:   Step 4: apply status/versionLabel/expirationDate
:           statusCode is added/updated (assumed to be valued)
:           versionLabel/expirationDate is added/updated if valued, or deleted otherwise
:           recursion is applied if @recurse='true', but only if the child object 
:           (concept|transaction) matches the statusCode of the main object
:
:   So if the main object was draft, then recursion stops when the first @statusCode!=draft 
:   is encounted. This is not considered an error so the user needs to check before/after 
:   calling this function
:
:   This function is called from:
:       decor-codeSystems       codeSystem          ItemStatusLifeCycle
:       decor-datasets          dataset             ItemStatusLifeCycle
:                               concept             ItemStatusLifeCycle
:       decor-scenarios         scenario            ItemStatusLifeCycle
:                               transaction         ItemStatusLifeCycle
:       decor-valuesets         valueSet            ItemStatusLifeCycle
:
:       decor-templates         templates           TemplateStatusLifeCycle
:       decor-template-mapping  templates           TemplateStatusLifeCycle
:
:   This function is currently not called for updating status of Releases and Issues
:
:)
declare %private function local:applyStatusProperties($object as element(), $originalStatus as xs:string?, $newStatus as xs:string?, $newExpirationDate as xs:string?, $newVersionLabel as xs:string?, $recurse as xs:boolean, $list as xs:boolean, $projectPrefix as xs:string) as element()*{
    let $statuschangeallowed    := art:isStatusChangeAllowable($object, $newStatus)
    let $sourceType             := decor:getObjectDecorType($object)
    
    let $update                 :=
        if ($list) then () else 
        if ($statuschangeallowed) then (
            if (empty($sourceType)) then () else hist:AddHistory($sourceType, $projectPrefix, if ($object[@statusCode='active']) then 'patch' else 'version', $object)
            ,
            if ($object/@statusCode) then
                update value $object/@statusCode with $newStatus
            else (
                update insert attribute statusCode {$newStatus} into $object
            ),
            if ($newVersionLabel) then
                if ($object/@versionLabel) then
                    update value $object/@versionLabel with $newVersionLabel
                else (
                    update insert attribute versionLabel {$newVersionLabel} into $object
                )
            else (
                update delete $object/@versionLabel
            ),
            if ($newExpirationDate) then
                if ($object/@expirationDate) then
                    update value $object/@expirationDate with $newExpirationDate
                else (
                    update insert attribute expirationDate {$newExpirationDate} into $object
                )
            else (
                update delete $object/@expirationDate
            )
            ,
            if ($object/self::transaction[@effectiveDate]) then () else (
                update insert attribute effectiveDate {($object/ancestor-or-self::*[@effectiveDate]/@effectiveDate)[last()]} into $object
            )
        ) else ()
    
    return (
        <response>{$object/@*, if ($statuschangeallowed) then 'OK' else 'Illegal status transition.'}</response>
        ,
        (:do recursion, but stop as soon we get to an artifact with a different status than our main artifact to respect the status machine:)
        if ($recurse) then (
            if ($object[name()=('dataset','concept')]) then
                for $child in $object/concept[@statusCode=$originalStatus]
                return local:applyStatusProperties($child, $originalStatus, $newStatus, $newExpirationDate, $newVersionLabel, $recurse, $list, $projectPrefix)
            else if ($object[name()=('scenario','transaction')]) then
                for $child in $object/transaction[@statusCode=$originalStatus]
                return local:applyStatusProperties($child, $originalStatus, $newStatus, $newExpirationDate, $newVersionLabel, $recurse, $list, $projectPrefix)
            else ()
        ) else ()
    )
};

declare %private function local:checkLock($object as element()?, $recurse as xs:boolean) as element()* {
    (:note: does not work for community locks. need value for 3rd argument:)
    decor:getLocks($object/@id, $object/@effectiveDate, (), false())
    ,
    if ($recurse) then (
        if ($object[name()=('dataset','concept')]) then
            for $child in $object/concept
            return local:checkLock($child,$recurse)
        else if ($object[name()=('scenario','transaction')]) then
            for $child in $object/transaction
            return local:checkLock($child,$recurse)
        else ()
    ) else ()
};

let $statusChange   := if (request:exists()) then request:get-data()/statusChange else (
    <statusChange prefix="demo1-" ref="2.16.840.1.113883.3.1937.99.62.3.10.11" effectiveDate="2012-03-11T00:00:00" statusCode="active" versionLabel="" expirationDate="" actionText="Status op 'actief' zetten" recurse="true" list="true"/>
)

(:get decor file containing object for permission check:)
let $decor          := 
    if (string-length($statusChange/@prefix) > 0) then 
        art:getDecorByPrefix($statusChange/@prefix)
    else (
        $get:colDecorData
    )
(:get object for reference:)
let $object         :=
    if ($statusChange/@effectiveDate[string-length()>0]) then
        $decor//*[@id=$statusChange/@ref][@effectiveDate=$statusChange/@effectiveDate][not(ancestor::history | ancestor::issues | ancestor::example)]
    else (
        $decor//*[@id=$statusChange/@ref][self::transaction[empty(@effectiveDate)]][not(ancestor::history | ancestor::issues | ancestor::example)]
    )

let $recurse        := if ($statusChange/@recurse castable as xs:boolean) then xs:boolean($statusChange/@recurse) else (false())
let $list           := if ($statusChange/@list castable as xs:boolean) then xs:boolean($statusChange/@list) else (false())
let $statusCode     := if (string-length($statusChange/@statusCode)>0) then $statusChange/@statusCode else ()
let $versionLabel   := if (string-length($statusChange/@versionLabel)>0) then $statusChange/@versionLabel else ()
let $expirationDate := 
    if ($statusChange/@expirationDate castable as xs:dateTime) 
    then $statusChange/@expirationDate
    else if ($statusChange/@expirationDate castable as xs:date)
    then concat($statusChange/@expirationDate,'T00:00:00')
    else ()

return
    <response>
    {
        $statusChange/@*
    }
    {
        if ($object) then (
            if (decor:canLock($object)) then (
                if ($object[self::scenario | self::transaction][descendant::*[@minimumMultiplicity = ('', '?')] | descendant::*[@maximumMultiplicity = ('', '?')]]) then (
                    <response>
                        <error>Status change not allowed while one or more transactions have concepts with undefined minimumMultiplicities and/or maximumMultiplicities. Found: {count($object/descendant::*[@minimumMultiplicity = ''] | $object/descendant::*[@maximumMultiplicity = ''])}. Please check: 
                        {
                            string-join(
                                for $reptemp in $object//representingTemplate[descendant::*[@minimumMultiplicity = ''] | descendant::*[@maximumMultiplicity = '']]
                                return
                                    concat('"', $reptemp/parent::*/name[1], '"')
                            , ', '
                            )
                        }</error>
                    </response>
                )
                else
                if ($object[self::template] and $recurse) then (
                    let $templatechain          := templ:getTemplateList($object/@id, (), $object/@effectiveDate, $statusChange/@prefix, (), false(), $templ:TREETYPELIMITEDMARKED)
                    
                    for $t in $templatechain//ref/parent::template
                    (:template/@ref will not match this. match only project local objects:)
                    let $template   := $decor//template[@id=$t/@id][@effectiveDate=$t/@effectiveDate]
                    return
                        if ($template) then (
                            let $locks          := local:checkLock($template, $recurse)
                            
                            return
                            if ($locks) then (
                                <response>{$template/@*, $locks}</response>
                            ) else (
                                local:applyStatusProperties($template, $template/@statusCode, $statusCode, $expirationDate, $versionLabel, $recurse, $list, $statusChange/@prefix)
                            )
                        )
                        else ()
                )
                else (
                    let $locks          := local:checkLock($object, $recurse)
                    
                    return
                    if ($locks) then (
                        <response>{$object/@*, $locks}</response>
                    ) else (
                        local:applyStatusProperties($object, $object/@statusCode, $statusCode, $expirationDate, $versionLabel, $recurse, $list, $statusChange/@prefix)
                    )
                )
            )
            else (
                <response>{$object/@*, decor:getLocks($object, false()), 'NO PERMISSION'}</response>
            )
        )
        else (
            <response>{$statusChange/@prefix, $statusChange/@ref, $statusChange/@effectiveDate, 'NOT FOUND'}</response>
        )
    }
    </response>