Module:Wikimédia France expenses
Module documentation
[create]
local p = {}
local STATES = { "request", "approval", "execution", "archiving", "verification" }
local NEXT_STATE = {
request = "approval",
approval = "execution",
execution = "archiving",
archiving = "verification",
verification = nil,
abort = nil
}
local ALLOWED_USERS = {
request = nil,
approval = {
["Pyb"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["Pradigue"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["Rémy Gerbet WMFr"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["CindyDavidWMFr"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["0x010C"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["0x010D"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
},
execution = {
["Pyb"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["Pradigue"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["Rémy Gerbet WMFr"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["CindyDavidWMFr"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["0x010C"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["0x010D"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
},
archiving = {
["Jonathan Balima WMFr"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["0x010E"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
},
verification = {
["Pradigue"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
["0x010D"] = os.time{year=2099, month=1, day=1, hour=0, min=0, sec=0},
}
}
ALLOWED_USERS["abort"] = ALLOWED_USERS["approval"]
local USERS_WITH_LIMITATIONS = {
["Rémy Gerbet WMFr"] = 10000,
["CindyDavidWMFr"] = 10000,
["0x010C"] = 10000
}
local BOX_TEMPLATE = 'Wikimédia_France_expenses/box'
local STATE_TEMPLATE = 'Wikimédia France expenses/state'
local SIMPLE_TEMPLATE = 'Wikimédia France expenses/simple'
local SIMPLE_T_TEMPLATE = 'Wikimédia France expenses/simple-header'
local SIMPLE_B_TEMPLATE = 'Wikimédia France expenses/simple-footer'
local CREATE_TEMPLATE = 'Wikimédia France expenses/create'
local DATA_PAGE = ':Wikimédia_France/Finances/Dépenses/'
local USER_DATA_PAGE = 'WMFrDépenses.json'
local STATE_CATEGORY = {
request = "Wikimédia France/Finances/Dépense/À approuver",
approval = "Wikimédia France/Finances/Dépense/À exécuter",
execution = "Wikimédia France/Finances/Dépense/À archiver",
archiving = "Wikimédia France/Finances/Dépense/À vérifier",
verification = "Wikimédia France/Finances/Dépense/Traitée",
abort = "Wikimédia France/Finances/Dépense/Abandonnée"
}
local DATE_CATEGORY = "Wikimédia France/Finances/Dépense"
local ERROR_CATEGORY = "Wikimédia France/Finances/Dépense/Erreur de signature"
local DATE_REGEX = "(%d+)-(%d+)-(%d+)"
local lang = mw.language.new( 'fr' )
local signData = {}
local categories = {}
local function checkState( data, stateName )
local user = data.state[ stateName ].u
local date = data.state[ stateName ].t
if ALLOWED_USERS[stateName] ~= nil then
allowedDate = ALLOWED_USERS[stateName][user]
if allowedDate == nil or date > allowedDate then
return "L'utilisateur " .. user .. " n'est pas autorisé à signer cette étape."
elseif (stateName == 'approval' or stateName == 'execution') and user == data.state['request'].u then
return "Le demandeur n'a pas le droit de signer cette étape."
elseif USERS_WITH_LIMITATIONS[user] ~= nil and data.amount > USERS_WITH_LIMITATIONS[user] then
return "Montant trop élevé pour pouvoir être signé par " .. user .. "."
end
end
return nil
end
local function getState(value)
result = STATES[1]
if value.state['abort'] ~= nil and value.state['abort'].u ~= nil then
return 'abort'
end
for _,stateName in pairs(STATES) do
if value.state[stateName] == nil or value.state[stateName].u == nil then
break
end
if checkState( value, stateName ) ~= nil then
break
end
result = stateName
end
return result
end
local function getInfoById(data, id)
for _,value in pairs(data) do
if tonumber(value.id) == tonumber(id) then
return value
end
end
return nil
end
local function convert_date(date)
year, month, day =s:match(date)
return os.time{year=year, month=month, day=day, hour=0, min=0, sec=0}
end
local function fromDateFilter(data, date)
filtered_data = {}
-- convert date to timestamp
year, month, day = date:match(DATE_REGEX)
date = os.time{year=year, month=month, day=day, hour=0, min=0, sec=0}
for _,value in pairs(data) do
if value.state.request.t >= date then
table.insert(filtered_data, value)
end
end
return filtered_data
end
local function toDateFilter(data, date)
filtered_data = {}
-- convert date to timestamp
year, month, day = date:match(DATE_REGEX)
date = os.time{year=year, month=month, day=day, hour=23, min=59, sec=59}
for _,value in pairs(data) do
if value.state.request.t <= date then
table.insert(filtered_data, value)
end
end
return filtered_data
end
local function nameFilter(data, name)
filtered_data = {}
name = mw.ustring.lower(mw.text.trim(name))
for _,value in pairs(data) do
if mw.ustring.find( mw.ustring.lower(value.name), name ) ~= nil then
table.insert(filtered_data, value)
end
end
return filtered_data
end
local function reasonFilter(data, reason)
filtered_data = {}
reason = mw.ustring.lower(mw.text.trim(reason))
for _,value in pairs(data) do
if mw.ustring.find( mw.ustring.lower(value.reason), reason ) ~= nil then
table.insert(filtered_data, value)
end
end
return filtered_data
end
local function minAmountFilter(data, amount)
filtered_data = {}
for _,value in pairs(data) do
if value.amount >= tonumber(amount) then
table.insert(filtered_data, value)
end
end
return filtered_data
end
local function maxAmountFilter(data, amount)
filtered_data = {}
for _,value in pairs(data) do
if value.amount <= tonumber(amount) then
table.insert(filtered_data, value)
end
end
return filtered_data
end
local function projectFilter(data, projects)
if #projects == 0 then
return data
end
filtered_data = {}
for _,value in pairs(data) do
for _,project in pairs(projects) do
project = mw.ustring.lower(mw.text.trim(project))
if value.project ~= nil then
if mw.ustring.find( mw.ustring.lower(value.project), project ) ~= nil then
table.insert(filtered_data, value)
end
end
end
end
return filtered_data
end
local function stateFilter(data, stateNames)
if #stateNames == 0 then
return data
end
filtered_data = {}
for _,value in pairs(data) do
currStateName = getState(value)
for _, name in ipairs(stateNames) do
if mw.ustring.lower(mw.text.trim(name)) == currStateName then
table.insert(filtered_data, value)
break
end
end
end
return filtered_data
end
local function getDigitalSignature(frame, user, id, stateName)
local sign
local title = mw.title.new( "User:" .. user .. '/' .. USER_DATA_PAGE )
if title.exists then
if signData[user] == nil then
signData[user] = mw.text.jsonDecode(frame:expandTemplate{ title = "User:" .. user .. '/' .. USER_DATA_PAGE, args = {} })
end
if signData[user][id] ~= nil then
return signData[user][id][stateName]
elseif signData[user][tonumber(id)] ~= nil then
return signData[user][tonumber(id)][stateName]
end
end
table.insert( categories, ERROR_CATEGORY )
return nil
end
local function _expense(frame, data, main_template, state_template)
local states = ""
local currentState = getState( data )
local nextStateToSign = NEXT_STATE[ currentState ]
if state_template ~= nil then
for key,stateName in pairs(STATES) do
local user = nil
local date = nil
local sign = nil
local baduser = nil
local isSelected = nil
if stateName == 'approval' and data.state["abort"] ~= nil then
stateName = 'abort'
end
if data.state[stateName] ~= nil then
user = data.state[stateName].u
date = data.state[stateName].t
end
if user ~= nil then
-- get digital signature from user json subpage
sign = getDigitalSignature(frame, user, data.id, stateName)
-- check if the user is allowed to sign that state
baduser = checkState( data, stateName )
if baduser ~= nil then
table.insert( categories, ERROR_CATEGORY )
end
end
if stateName == nextStateToSign or ( stateName == 'abort' and baduser ~= nil ) then
isSelected = "true"
end
states = states .. frame:expandTemplate{ title = state_template, args = {
nb = key,
name = stateName,
user = user,
date = lang:formatDate( "j F Y à H:i (e)", os.date("%Y%m%d%H%M00", date), false),
timestamp = date,
sign = sign,
baduser = baduser,
selected = isSelected,
} }
if stateName == 'abort' then
break
end
end
end
table.insert( categories, STATE_CATEGORY[ currentState ] )
table.insert( categories, DATE_CATEGORY )
return frame:expandTemplate{ title = main_template, args = {
id = data.id,
date = os.date("%Y-%m-%d", data.state.request.t),
applicant = data.state.request.u,
name = data.name,
project = data.project,
reason = data.reason,
amount = data.amount,
comment = data.comment,
state = currentState,
states = states
} }
end
local function dateToPageName(timestamp)
local year = tonumber( os.date('%Y', timestamp) )
local month = tonumber( os.date('%m', timestamp) )
if month < 7 then
year = year - 1
end
return DATA_PAGE .. tostring( year ) .. '-' .. tostring( year + 1 ) .. '.json'
end
function p.main(frame)
local args = frame:getParent().args
local format = args.format
local search = args.search
local create = args.create
local id = args.id
local exercice = tonumber(args.exercice)
local name = args.name
local reason = args.reason
local project = (args.project == nil or args.project == "") and {} or mw.text.split(args.project, ",", true )
local dateFrom = args.from
local dateTo = args.to
local amountMin = args.min
local amountMax = args.max
local state = (args.state == nil or args.state == "") and {} or mw.text.split( args.state, ",", true )
local searchOptions = ''
local text = ''
if format ~= nil then
searchOptions = 'data-format="' .. format .. '"'
end
if id ~= nil then
searchOptions = searchOptions .. ' data-id="' .. id .. '"'
local data = mw.text.jsonDecode(frame:expandTemplate{ title = dateToPageName(id), args = {} })
data = getInfoById( data, id )
if data ~= nil then
text = _expense(frame, data, BOX_TEMPLATE, STATE_TEMPLATE)
local categoriesString = ""
for _,cat in pairs(categories) do
categoriesString = categoriesString .. "\n[[Category:" .. cat .. "/" .. os.date("%Y", data.state.request.t) .. "]]"
end
return text .. "\n" .. categoriesString
end
elseif exercice ~= nil then
searchOptions = searchOptions .. ' data-exercice="' .. exercice .. '"'
local data = mw.text.jsonDecode(frame:expandTemplate{ title = DATA_PAGE .. tostring( exercice ) .. '-' .. tostring( exercice + 1 ) .. '.json', args = {} })
if #project > 0 then
searchOptions = searchOptions .. ' data-project="' .. args.project .. '"'
data = projectFilter(data, project)
end
if dateFrom ~= nil then
searchOptions = searchOptions .. ' data-from="' .. dateFrom .. '"'
data = fromDateFilter(data, dateFrom)
end
if dateTo ~= nil then
searchOptions = searchOptions .. ' data-to="' .. dateTo .. '"'
data = toDateFilter(data, dateTo)
end
if name ~= nil then
searchOptions = searchOptions .. ' name="' .. name .. '"'
data = nameFilter(data, name)
end
if reason ~= nil then
searchOptions = searchOptions .. ' reason="' .. reason .. '"'
data = reasonFilter(data, reason)
end
if amountMin ~=nil then
searchOptions = searchOptions .. ' data-min="' .. amountMin .. '"'
data = minAmountFilter(data, amountMin)
end
if amountMax ~=nil then
searchOptions = searchOptions .. ' data-max="' .. amountMax .. '"'
data = maxAmountFilter(data, amountMax)
end
if #state > 0 then
searchOptions = searchOptions .. ' data-state="' .. args.state .. '"'
data = stateFilter(data, state)
end
if format == 'simple' then
text = text .. frame:expandTemplate{ title = SIMPLE_T_TEMPLATE, args = {} } .. '\n'
if #data > 0 then
for _,value in pairs(data) do
text = text .. _expense(frame, value, SIMPLE_TEMPLATE, nil) .. '\n'
end
else
text = text .. '|-\n| colspan="8" | Aucune dépense correspondant aux critères.\n'
end
text = text .. frame:expandTemplate{ title = SIMPLE_B_TEMPLATE, args = {} }
else
for _,value in pairs(data) do
text = text .. _expense(frame, value, BOX_TEMPLATE, STATE_TEMPLATE) .. "<br>"
end
end
end
if search ~= nil then
text = '<div class="eb-search"><div class="eb-search-inputs" ' .. searchOptions .. '></div><div class="eb-search-results">\n' .. text .. '\n</div></div>'
end
if create ~= nil then
text = text .. '\n' .. frame:expandTemplate{ title = CREATE_TEMPLATE, args = {} }
end
return text
end
return p