Model 109

Changes

application language

'Periods': collection ['Period']
	'Period Order': ordered-graph .'First' ( ?'Yes' || ?'No'>'Predecessor' ) = partition
	= partition .'Period Size' as 'Period Start' in 'Period Order' (
		'1' = .'Orders'* as $ on $ .'Order Date'
	) {
	'Period': text = key
	'Period Start': number 'days' = parameter
	'First': stategroup = parameter (
		'No' {
			'Predecessor': text -> ^ siblings in 'Period Order'[] = parameter
		}
		'Yes' { }
	)
	'Orders': reference-set -> ^ .'Orders'* = branch '1'
}
'Tasks': collection ['Task']
	'Dependencies': acyclic-graph
{
	'Task': text
	'Predecessor': stategroup (
		'None' { }
		'After' {
			'Task': text -> ^ siblings in 'Dependencies'[]
		}
	)
}
'Milestones': collection ['Task']
	'Milestone Order': acyclic-graph = 'Dependencies'
{
	'Task': text -> ^ .'Tasks'[]
	'Predecessor': stategroup = on >'Task' as $'Step' in 'Dependencies' => switch $'Step'.'Predecessor' (
		|'None'  => 'None' ( )
		|'After' as $ => switch siblings in 'Milestone Order'>[ $ >'Task'] (
			| node  as $ => 'After' ( 'Task' = $ )
			| none  => recurse on $^ $'Step' = $ >'Task'
		)
	) (
		'None' { }
		'After' {
			'Task': text -> ^ siblings in 'Milestone Order'[] = parameter
		}
	)
}
'Available Shifts': collection ['Shift'] = union (
	'1' = as $ on $ >'Shift'
) {
	'Shift': text -> ^ ^ >'Work Center'.'Shifts'[] = key
	'1': reference-set -> ^ = branch '1' // no collection steps, only a parent step
	'Available Slots': collection ['Slot'] = union (
		'1' = <'1'* as $ on $ >'Time Slot' // rejected by model 108
	) {
		'Slot': text -> ^ >'Shift'.'Time Slots'[] = key
	}
}
'Favorite Product Ordered': stategroup = switch .'Ordered Products'>[ >'Favorite Product'] (
	| node  => 'Yes' ( )
	| none  => 'No' ( )
) ( ... )
// old
'Product': text -> ^ .'Products'[] as $
	where 'Ordered' -> ^ .'Ordered Products'[ $ ]

// new
'Product': text -> ^ .'Products'[] as $
	where 'Ordered' -> ^ .'Ordered Products'>[ $ ]
// old
'Parts Cost': number 'euro' = sum .'Parts'* .'Part Price'
'# Products': number 'items' = count .'Products'*

// new
'Parts Cost': number 'euro' = sum ( .'Parts'* .'Part Price' )
'# Products': number 'items' = count ( .'Products'* )
// old
'Previous Year': text -> ^ sibling in ( 'Ordered Years' )
'Self or Sibling': text -> ^ sibling || self
'Other Year': text -> sibling
'Next Years': reference-set -> sibling in ( inverse 'Ordered Years' ) * .'First'?'No' = inverse >'Previous Year'
'Weeks of other years': reference-set -> sibling * .'Weeks'* = inverse >'Linked Year'
'Later Years': collection ['Year'] = union (
	'this' = <'Next Years'[ sibling * ]
) { ... }

// new
'Previous Year': text -> ^ siblings in 'Ordered Years'[]
'Self or Sibling': text -> ^ siblings || self []
'Other Year': text -> siblings []
'Next Years': reference-set -> siblings in inverse 'Ordered Years'* .'First'?'No' = inverse >'Previous Year'
'Weeks of other years': reference-set -> siblings * .'Weeks'* = inverse >'Linked Year'
'Later Years': collection ['Year'] = union (
	'this' = <'Next Years'[ siblings in inverse 'Ordered Years'* ]
) { ... }
'Open Orders': stategroup = switch .'Orders'* .'Status'?'Open' (
	| nodes as $ => 'Several' ( '# Orders' = count ( $ * ) )
	| node  => 'One' ( )
	| none  => 'None' ( )
) ( ... )

Without | nodes, this switch reports no valid 'upper bound cardinality for set' found for 'match nodes' option 'no'; without | none, no valid 'lower bound cardinality for set' found for 'match none' option 'no'. The old switch on a node path in a derivation always had a | none case. upgrade/108 drops that case from an existence switch on a path that always produces a node. A switch ... is ( ... ) keeps its | none case, which is valid there, and a switch on a set keeps every case it had:

// old
'Price': number 'cents' = switch >'Product' (
	| none => 0
	| node as $ => $ .'Price'
)

// new
'Price': number 'cents' = switch >'Product' ( | node  as $ => $ .'Price' )
'Lines': reference-set -> downstream ^ ^ .'Orders'>[ ^ ] .'Lines'* = inverse >'Contact'
'# Deliveries': number 'items' = count ( .'Orders'* .'Lines'* >'Product'.'Deliveries'* )
'# Members': number 'items' = count ( >'Region'<'Customers'[ >'Country'.'Cities'* .'Customers'* ] )
// ERROR: equality constraint violation for 'subset of reference set': the signature of 'Customers' declares no filter on 'Status'
'# Active': number 'items' = count ( >'Region'<'Customers'[ >'Country'.'Cities'* .'Customers'* .'Status'?'Active'] )
// accepted: the filter follows the subset step
'# Active': number 'items' = count ( >'Region'<'Customers'[ >'Country'.'Cities'* .'Customers'* ] .'Status'?'Active' )

A siblings step after the head walks from the node that the head selects, while the signature walks its siblings from the entry itself; the two do not match, so this is an error. Without a head, a siblings step walks from the entry itself, like the signature. It is accepted where the signature walks the same siblings or the same graph, and also where the signature walks a graph and the path takes all siblings. A siblings || self step walks the whole collection and may follow a head. ‘Taken over next year’ below sits on a supplier of a year; ‘Other Year’ is -> siblings [] on the year:

'Taken over next year': reference-set -> ^ siblings in inverse 'Ordered Years'* .'Suppliers'* .'Taken Over'?'Yes' = inverse >'Supplier Last Year'
// the head `^ >'Other Year'` selects the year by a plain sibling reference: accepted
'# Taken over by other year': number 'items' = count ( <'Taken over next year'[ ^ >'Other Year'.'Suppliers'* ] )
// ERROR: equality constraint violation for 'subset of reference set': the graph step after the head walks from the other year
'# Taken over after other year': number 'items' = count (
	<'Taken over next year'[ ^ >'Other Year' siblings in inverse 'Ordered Years'* .'Suppliers'* ]
)
'Lines': reference-set -> ^ .'Orders'* .'Lines'* = inverse >'Line'
'Line found': stategroup = switch <'Lines'[ >'Order'.'Lines'* ] (
	| node  => 'Yes' ( )
	| none  => 'No' ( )
) ( ... )
// old
'' = .'Orders'* >'Debtor'

// new
'' = .'Orders'* as $ on $ >'Debtor' // group: one entry per debtor
'this' = <'Shifts'[ >'Work Center'.'Shifts'* ] // merge: one entry per node the path produces
'Order': text -> ^ .'Order Numbers'[] as $
	where 'Invoice' -> ^ .'Invoices'>[ $ ] // unique: 'Order' is the key
'Supplier': text -> ^ .'Suppliers'[] as $
	where 'Contract' -> ^ .'Contracts'>[ $ ] // repeats: two orders can find the same contract
	where 'Line' -> .'Lines'>[ $ ] // repeats, but every order looks up in its own lines
'# Invoice debtors': number 'items' = count ( .'Orders'* .'Order'&'Invoice'>'Debtor' ) // accepted
// ERROR: no valid 'reference navigation support' found for 'type' option 'dependency'
'# Contract debtors': number 'items' = count ( .'Orders'* .'Supplier'&'Contract'>'Debtor' )
'# Line debtors': number 'items' = count ( .'Orders'* .'Supplier'&'Line'>'Debtor' ) // accepted
'Address': text ~> ^ >'Customer'.'Status'?'Active' ^ .'Addresses'[] // takes the filter of the key below
'Any Address': text -> ^ >'Customer'.'Addresses'[] // skips it
'Delivery Addresses': collection ['Address'] = union (
	'ok' = .'Lines'* as $ on $ >'Address' // accepted
	'bad' = .'Lines'* as $ on $ >'Any Address' // ERROR: equality constraint violation for 'key reference dependency'
) {
	'Address': text -> ^ >'Customer'.'Status'?'Active' ^ .'Addresses'[] = key
}

The reverse is allowed: a value path may filter where the reference does not.

'Ref': text -> siblings [] as $
	where 'in S' -> $ .'Status'?'Open'
'Ref in S': text -> siblings [] .'Status'?'Open' = .'Ref'&'in S' // accepted

'Lines': collection ['Line'] = flatten (
	'open' = .'Orders'* .'Status'?'Open' as $ ( 'Debtor' = $ ^ ^ >'Debtor' )
) join - {
	'Line': text = key
	'Debtor': text -> ^ .'Debtors'[] = parameter
}
'Bundle': command {
	'Selection': stategroup ( ... )
	'Picks': collection ['Line'] @default: candidates &'Open' where 'Selected' {
		'Line': text -> ^ ^ .'Lines'[]
			where 'Old' ~> ^ .'Selection'?'All' ^ ^ .'Lines'[] .'Done'?'No' // ERROR: equality constraint violation for 'context entity'
			where 'Selected' ~> ^ .'Selection'?'All' // accepted
			where 'Open' ~> ^ ^ .'Lines'[] .'Done'?'No' // accepted
	}
} => update .'Item' = "bundled"
'Order Copy': ordered-graph .'First' ( ?'Yes' || ?'No'>'Previous Copy' ) // ERROR: no valid 'value source' found for 'edge reference'
'Order Copy': ordered-graph .'First' ( ?'Yes' || ?'No'>'Previous Base' ) // accepted

'Previous Base': text -> ^ siblings in 'Order Copy'[]
'Previous Copy': text -> ^ siblings in 'Order Copy'[] = >'Previous Base'
// old
'Lines': collection ['Line'] = flatten (
	'Order' = .'Orders'* as $'o' (
		'Line' = $'o'.'Number'
		'Amount' = $'o'.'Amount'
	)
) join - {
	'Line': text = parameter
	'Amount': number 'euro' = parameter
}

// new
'Lines': collection ['Line'] = flatten (
	'Order' = .'Orders'* as $'o' ( 'Amount' = $'o'.'Amount' )
) join - {
	'Line': text = key
	'Amount': number 'euro' = parameter
}
'Open'
	has-todo: user
		@label: concat ( "Open order of ", .'Company' ) // accepted: 'Company' is a property of the root
		@description: "Handle this order"
		@style: switch .'Mode' ( // accepted: 'Mode' is a stategroup of the root
			|'Normal' => foreground
			|'Urgent' => error
		)
{ }
'Closed'
	has-todo: user @label: .'Number' // ERROR: 'property' `Number` was not found in 'attributes'
{ }
// old
'No' {
	has-todo: user @description: "Solve this problem in its module."
}

// new
'No'
	has-todo: user @description: "Solve this problem in its module."
{ }

'Open Problems': collection ['Problem'] = .'Problems'* .'Handled'?'No' {
	'Problem': text -> ^ .'Problems'[] .'Handled'?'No' = key
	'Reported': stategroup = 'No' ( ) (
		'No'
			has-todo: user @description: "Report this problem."
		{ }
		'Yes' { }
	)
}

A todo on a root, group or collection node is moved by upgrade/108 to the one state of a new derived stategroup 'Todo item', the node’s last property, hidden from the views. Its requirement gets a leading ^ on every path that started at the node, because it now starts at the state’s node:

// old
'Problems': collection ['Problem'] {
	has-todo: user where ( .'Status'?'Open' ) @description: "Solve this problem in its module."
	'Problem': text
	'Status': stategroup ( ... )
}

// new
'Problems': collection ['Problem'] {
	'Problem': text
	'Status': stategroup ( ... )
	'Todo item': stategroup = 'Yes' ( ) @hidden (
		'Yes'
			has-todo: user where ( ^ .'Status'?'Open' ) @description: "Solve this problem in its module."
		{ }
	)
}

The requirement still controls when the todo applies. Replace the generated stategroup by one of your own when you want an explicit state, as ‘Handled’ above. A todo on the parameters of a command or action has no derived stategroup to carry it and is dropped. A node that already has a property 'Todo item' cannot take the new one: rename that property before you upgrade.

After the upgrade

./alan upgrade runs upgrade/108 on every model 108 (platform before-partitions.1) and migration source model. The transformation converts what it can, and the build that follows reports what is left: that is the work by hand. These are the errors it reports for the changes above, each with its cause and the change that shows the fix.

Errors that converted code can report:

Errors in code that was not converted, such as a model upgraded by hand: