Le sujet du script qui suit est de ventiler une valeur numérique (une somme d'argent probablement) sur une série de lignes contenant chacune une valeur numérique, au prorata de cette valeur. Le cas réel rencontré est celui de la répartition d'une remise sur une commande qu'il fallait répartir sur les diverses lignes qui la composaient (afin que SAP qui ne savait pas ce qu'était une remise globale accepte ladite remise). J'ai rencontré ce problème une seconde fois: il était nécessaire de distribuer des frais de transports sur les lignes d'une commande. Deux contraintes doivent être respectées:
- Toutes les valeurs doivent être entières (pas de bout de centimes, please)
- On ne perd rien (et donc les centimes manquants doivent être rajoutés sur la dernière ligne)
Dans cet exemple, voilà notre payload en entrée:
{
"share": 1234,
"to": [
{
"value": 234
},
{
"value": 456
},
{
"value": 678
}
]
}
"share": 1234,
"to": [
{
"value": 234
},
{
"value": 456
},
{
"value": 678
}
]
}
Il s'agit donc de répartir la somme 1234 au prorata des sommes des lignes: 234, 456 et 678 et donc ajouter un champ dans chaque objet contenant les valeurs 211, 411, 611. L'application stricte d'une répartition nous ferait perdre 1. Cette valeur est rajouté à la dernière valeur qui devient 612. Le résultat attendu est donc:
[
{
"value": 234,
"shared": 211
},
{
"value": 456,
"shared": 411
},
{
"value": 678,
"shared": 612
}
]
{
"value": 234,
"shared": 211
},
{
"value": 456,
"shared": 411
},
{
"value": 678,
"shared": 612
}
]
La solution tient en quelques lignes (comme souvent avec DataWeave):
%dw 2.0
output application/json
fun total(a) = sum(a.value)
fun totalShared(v, a) = sum(total(a) then (t) -> a map floor($.value/t*v))
fun distribute(v, a) = total(a) then (t) -> (
a map {
"value": $.value,
"shared": floor($.value/t*v) + if ($$ == sizeOf(a)-1) v-totalShared(v, a) else 0
}
)
---
distribute(payload.share, payload.to)
output application/json
fun total(a) = sum(a.value)
fun totalShared(v, a) = sum(total(a) then (t) -> a map floor($.value/t*v))
fun distribute(v, a) = total(a) then (t) -> (
a map {
"value": $.value,
"shared": floor($.value/t*v) + if ($$ == sizeOf(a)-1) v-totalShared(v, a) else 0
}
)
---
distribute(payload.share, payload.to)
Le cœur de ce script est l'appel à la fonction DataWeave "map" qui effectue le règle de trois attendue. Noter l'utilisation de la fonction "then" afin de ne pas répéter le calcul des totaux (ok, c'est essentiellement pour faire joli vu le nombre très réduit de lignes 😏 ). L'exception finale (afin de récupérer les unités perdues) est gérée par un "if" qui teste pour savoir si on est en train de traiter la dernière ligne. Ce traitement à besoin de deux fonctions utilitaires: "total" qui somme le total des valeurs de toutes les lignes et "totalShared" qui somme les répartitions obtenues par l'application brute de la règle de trois (c'est à dire qui perd les bouts d'unités).
Ce code est plutôt simple et s'il figure dans ce blog, c'est que j'ai vu des consultants en grande difficulté pour l'écrire. Il m'a paru donc utile de l'exposer ici car ce besoin est très récurent finalement.
_____________________________________________________________________________
The subject of the following script is to distribute a numerical value (probably a sum of money) over a series of lines each containing a numerical value, in proportion to that value. The actual case encountered is that of distributing a discount on an order, which had to be spread over the various lines that made it up (so that SAP, which didn't know what a global discount was, would accept the said discount). I encountered this problem a second time: it was necessary to distribute transport costs on the lines of an order. Two constraints must be respected:
- All values must be whole (no bits of cents, please)
- Nothing is lost (so any missing cents must be added on the last line).
In this example, here's our input payload:
{
"share": 1234,
"to": [
{
"value": 234
},
{
"value": 456
},
{
"value": 678
}
]
}
"share": 1234,
"to": [
{
"value": 234
},
{
"value": 456
},
{
"value": 678
}
]
}
We therefore need to distribute the sum 1234 in proportion to the sums of lines 234, 456 and 678, adding a field in each object containing the values 211, 411 and 611. The strict application of a distribution would cause us to lose 1. This value is added to the last value, which becomes 612. The expected result is:
[
{
"value": 234,
"shared": 211
},
{
"value": 456,
"shared": 411
},
{
"value": 678,
"shared": 612
}
]
{
"value": 234,
"shared": 211
},
{
"value": 456,
"shared": 411
},
{
"value": 678,
"shared": 612
}
]
The solution can be summed up in a few lines (as is often the case with DataWeave):
%dw 2.0
output application/json
fun total(a) = sum(a.value)
fun totalShared(v, a) = sum(total(a) then (t) -> a map floor($.value/t*v))
fun distribute(v, a) = total(a) then (t) -> (
a map {
"value": $.value,
"shared": floor($.value/t*v) + if ($$ == sizeOf(a)-1) v-totalShared(v, a) else 0
}
)
---
distribute(payload.share, payload.to)
output application/json
fun total(a) = sum(a.value)
fun totalShared(v, a) = sum(total(a) then (t) -> a map floor($.value/t*v))
fun distribute(v, a) = total(a) then (t) -> (
a map {
"value": $.value,
"shared": floor($.value/t*v) + if ($$ == sizeOf(a)-1) v-totalShared(v, a) else 0
}
)
---
distribute(payload.share, payload.to)
The heart of this script is the call to the DataWeave "map" function, which performs the expected rule of three. Note the use of the "then" function so as not to repeat the calculation of totals (ok, this is essentially for show, given the very small number of lines 😏 ). The final exception (to recover lost units) is handled by an "if" that tests whether the last line is being processed. This processing requires two utility functions: "total", which sums the total of the values of all the lines, and "totalShared", which sums the distributions obtained by the raw application of the rule of three (i.e. who loses the bits of units).
This code is quite simple, and the reason it appears in this blog is that I've seen consultants having great difficulty writing it. So I thought it would be useful to present it here, as this is a recurring need.
Aucun commentaire:
Enregistrer un commentaire