V20 Holidays - Can they be global across all departments?

NZphoneguy

Gold Partner
Advanced Certified
Joined
Nov 28, 2024
Messages
5
Reaction score
1
Hello, last night I upgraded a V18 system to V20. They had 98 Inbound Rules, all with different office hours so I had to create a department for each one and add the daytime hours etc..
Also as per all of my 18 to 20 upgrades the outside office hours destinations all disappeared so I had to re-enter all of them which was a real drag.

With Easter coming up I now need to add holidays. My question is can I set the holidays as global so they apply to all departments or do I have to enter the holidays to every one of the 98 departments which will take 3-4 hours.

Thanks
 
In each. VOIPTools has a free holiday importer tool. You could check out. IIRC it requires software on the server but in this case might well be worth it.
 
  • Like
Reactions: VoIPTools
It's free: 3CX Holiday Importer

1. Select the country
2. Select the year
3. Select the desired holidays
4. Select the department
5. Press "save"
6. Select the next department
7. Press "save"

1741870015825.png
 
@VoIPTools
Thank you very much for your work and for making the tool free.

We like to use automated solutions and increasingly use PowerShell because it's convenient, like this:

3cx-manage-holidays.ps1

Code:
# example: sets and / or removes 3CX holidays in various departments at once

# authorization data
$tcxurl = "mypbx.mydomain.de"   # needs port if not 443
$jsonauthbody = (@{
    Username="988"              # 3CX user, maybe email address
    Password="Super_Secret0815" # 3CX user password
    SecurityCode=""
} | ConvertTo-Json)

# array of department numbers which will be affected, number is from 3CX admin / call handling / extension
$departments = @( "GRP2", "GRP0000", "GRP0001", "GRP0003", "GRP0002" )

# array of strings with holiday names which will be deleted (asterisk for match all in front and/or end is possible), if empty none will deleted
$holidays_to_remove = @( "easter*", "laborday", "christmas*", "newyear*", "test*", "*2025", "*year*" )
 
# array of hashes with holiday params which will be added, if empty none will added
# HINT: do not use asterisk in department name in any way
$holidays_to_add = @(   #   name of holiday                audio file name            is recurring   is the whole day              start date   start time if not             end date   end time if not
                        #                                        may empty              every year   time will ignored             yyyy-mm-dd   whole day  hh:mm            yyyy-mm-dd   whole day hh:mm
                        @{Name="easter2025";    HolidayPrompt="easter.wav";      IsRecurrent=$false; IsWholeDay=$true;  Startdate="2025-04-18"; Starttime="00:00"; Enddate="2025-04-21";  Endtime="00:00"},
                        @{Name="easter2026";    HolidayPrompt="easter.wav";      IsRecurrent=$false; IsWholeDay=$true;  Startdate="2025-04-03"; Starttime="00:00"; Enddate="2025-04-06";  Endtime="00:00"},
                        @{Name="laborday";      HolidayPrompt="laborday.wav";    IsRecurrent=$true;  IsWholeDay=$true;  Startdate="2025-05-01"; Starttime="00:00"; Enddate="2025-05-01";  Endtime="00:00"},
                        @{Name="christmas24";   HolidayPrompt="christmas.wav";   IsRecurrent=$true;  IsWholeDay=$false; Startdate="2025-12-24"; Starttime="13:00"; Enddate="2025-12-24";  Endtime="00:00"},
                        @{Name="christmas2526"; HolidayPrompt="christmas.wav";   IsRecurrent=$true;  IsWholeDay=$true;  Startdate="2025-12-25"; Starttime="00:00"; Enddate="2025-12-26";  Endtime="00:00"},
                        @{Name="newyeareve";    HolidayPrompt="newyeareve.wav";  IsRecurrent=$true;  IsWholeDay=$false; Startdate="2025-12-31"; Starttime="13:00"; Enddate="2025-12-31";  Endtime="00:00"},
                        @{Name="newyear";       HolidayPrompt="newyear.wav";     IsRecurrent=$true;  IsWholeDay=$true;  Startdate="2025-01-01"; Starttime="00:00"; Enddate="2025-01-01";  Endtime="00:00"},
                        @{Name="prayer2025";    HolidayPrompt="prayer.wav";      IsRecurrent=$false; IsWholeDay=$true;  Startdate="2025-11-19"; Starttime="00:00"; Enddate="2025-11-19";  Endtime="00:00"},
                        @{Name="oldyearparty";  HolidayPrompt="wehaveparty.wav"; IsRecurrent=$false; IsWholeDay=$true;  Startdate="2025-01-04"; Starttime="00:00"; Enddate="2025-01-04";  Endtime="00:00"},
                        @{Name="test01";        HolidayPrompt="";                IsRecurrent=$true;  IsWholeDay=$false; Startdate="2025-03-01"; Starttime="14:01"; Enddate="2025-03-01";  Endtime="16:02"}
                    )


# === work starts here with authorization
$LoginResponse = Invoke-RestMethod -Uri "https://$tcxurl/webclient/api/Login/GetAccessToken" -Body $jsonauthbody -Method POST -ContentType "application/json"
$headers = @{Authorization = "Bearer $($LoginResponse.Token.access_token)"}


Function ReformatHolidaysToAdd
{
    [CmdletBinding()]
    param
    (
        $holidays_to_add
    )
 
    $holiaddcont=[System.Collections.ArrayList]@()
    Foreach ($holiadd in $holidays_to_add)
    {
        If ($holiadd.IsWholeDay -eq $true)
        {
            $tosd = "P0D"
            $toed = "P1D"
        } Else
        {
            If ([int]($holiadd.Starttime.Split(':')[0]) -eq 0)
            {
                If ([int]($holiadd.Starttime.Split(':')[1]) -eq 0)
                {
                    $tosd = "P0D"
                } Else
                {
                    $tosd = "PT0H$([int]($holiadd.Starttime.Split(':')[1]))M"
                }
            } Else
            {
                If ([int]($holiadd.Starttime.Split(':')[1]) -eq 0)
                {
                    $tosd = "PT$([int]($holiadd.Starttime.Split(':')[0]))H"
                } Else
                {
                    $tosd = "PT$([int]($holiadd.Starttime.Split(':')[0]))H$([int]($holiadd.Starttime.Split(':')[1]))M"
                }
            }
       
            If ([int]($holiadd.Endtime.Split(':')[0]) -eq 0)
            {
                If ([int]($holiadd.Endtime.Split(':')[1]) -eq 0)
                {
                    $toed = "P1D"
                } Else
                {
                    $toed = "PT0H$([int]($holiadd.Endtime.Split(':')[1]))M"
                }
            } Else
            {
                If ([int]($holiadd.Endtime.Split(':')[1]) -eq 0)
                {
                    $toed = "PT$([int]($holiadd.Endtime.Split(':')[0]))H"
                } Else
                {
                    $toed = "PT$([int]($holiadd.Endtime.Split(':')[0]))H$([int]($holiadd.Endtime.Split(':')[1]))M"
                }
            }
        }
        If ($holiadd.IsRecurrent -eq $true)
        {
            $years = 0
            $yeare = 0
        } Else
        {
            $years = [int]($holiadd.Startdate.Split('-')[0])
            $yeare = [int]($holiadd.Enddate.Split('-')[0])
        }
        $acthash=@{Name = $holiadd.Name; IsRecurrent = $holiadd.IsRecurrent; HolidayPrompt = $holiadd.HolidayPrompt;
            Day=[int]($holiadd.Startdate.Split('-')[2]); Month=[int]($holiadd.Startdate.Split('-')[1]); Year=$years; TimeOfStartDate=$tosd;
            DayEnd=[int]($holiadd.Enddate.Split('-')[2]); MonthEnd=[int]($holiadd.Enddate.Split('-')[1]); YearEnd=$yeare; TimeOfEndDate=$toed }
        $holiaddcont.Add($acthash) | Out-Null
    }
    $holiaddcont
} # end Function ReformatHolidaysToAdd

Function ReformatHolidaysToRemove
{
    [CmdletBinding()]
    param
    (
        $holidays_to_remove,
        $holidays_original
    )
 
    $holirestcont=[System.Collections.ArrayList]@()
    Foreach ($holiorig in $holidays_original)
    {
        $found = $false
        Foreach ($holirem in $holidays_to_remove.Replace("*",".*"))
        {
            If ($holiorig.Name -match "^$($holirem)`$")
            {
                $found = $true
                Break
            }
        }
        If ($found -eq $false)
        {
            $holirestcont.Add($holiorig) | Out-Null
        }
    }
    $holirestcont.ToArray()
} # end Function ReformatHolidaysToRemove


# main ...
$groups=(Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Groups?`$filter=not startsWith(Name, '___FAVORITES___')&`$select=Id,Number" -headers $headers -Method GET -ContentType "application/json").Value
Foreach ($group in $groups)
{
    If ($departments -contains $group.Number)
    {
        $holis=(Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Groups($($group.Id))?`$select=OfficeHolidays&`$expand=OfficeHolidays" -headers $headers -Method GET -ContentType "application/json")
        $holichangedcontent = (ReformatHolidaysToRemove -holidays_to_remove $holidays_to_remove -holidays_original $holis.OfficeHolidays)
        $holichangedcontent = $holichangedcontent+(ReformatHolidaysToAdd -holidays_to_add $holidays_to_add)
        If ($holichangedcontent.Count -eq 0 )
        {
            $holichangedcontent=@()
        }
        $holiaddcomplete = @{AllowCallService=$true; OverrideHolidays=$true; OfficeHolidays=$holichangedcontent } | ConvertTo-Json
        Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Groups($($group.Id))" -headers $headers -Method PATCH -body $holiaddcomplete -ContentType "application/json" | Out-Null
    }
}

exit

I hope there aren't any typos or copy-paste errors. This is a complete example. You can run it without any restrictions.
 
Last edited:
  • Like
Reactions: GMark
We like to use automated solutions and increasingly use PowerShell because it's convenient, like this:

3cx-manage-holidays.ps1
I modified the line
Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Groups($($group.Id))" -headers $headers -Method PATCH -body $holiaddcomplete -ContentType "application/json; charset=utf-8" | Out-Null
(otherwise it failed on "Brückentag" in the name of a holiday...)
 
  • Like
Reactions: fxbastler
I modified the line

Invoke-RestMethod -uri "https://$tcxurl/xapi/v1/Groups($($group.Id))" -headers $headers -Method PATCH -body $holiaddcomplete -ContentType "application/json; charset=utf-8" | Out-Null

(otherwise it failed on "Brückentag" in the name of a holiday...)
Always these umlauts.

Avoiding and getting along without them is our solution. Es hat sich einfach nicht bewaehrt :)
 
  • Haha
Reactions: GMark

Latest Posts

Forum statistics

Threads
111,964
Messages
590,000
Members
164,869
Latest member
hpgitsupport