[Bps-public-commit] rt-extension-repeatticket branch master updated. 2.00-7-gfe5c800

BPS Git Server git at git.bestpractical.com
Wed Sep 20 14:38:52 UTC 2023


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt-extension-repeatticket".

The branch, master has been updated
       via  fe5c8000681b9a8afd8ceabc622958ff18759f03 (commit)
       via  e47ede021358764389cc7e420cfcd8b9f874eee6 (commit)
       via  3b974aabb6109740e30fd33fa15b6ebdef9b739d (commit)
       via  2c0b210aa9022ed6123121fdfb346493ab90f8aa (commit)
       via  dc61451e68942e67e1c303598a7c8088f280167c (commit)
       via  f0977a3f58bf4da65f0a798234c5baea6eac6f84 (commit)
      from  e11a4122c2945d4fb12f0ff0a6b509ce1442d11e (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit fe5c8000681b9a8afd8ceabc622958ff18759f03
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Sep 20 09:29:12 2023 -0400

    Bump version to 2.01

diff --git a/Changes b/Changes
index de01aa5..0868031 100644
--- a/Changes
+++ b/Changes
@@ -1,5 +1,10 @@
 Revision history for RT-Extension-RepeatTicket
 
+2.01 2023-09-20
+
+ - Add new simple mode
+ - Add repeat ticket preview mode
+
 2.00 2021-04-20
  - Add RT 5 Support(RT 4 users need to use 1.* versions)
 
diff --git a/MANIFEST b/MANIFEST
index 0ce8b27..061b5b5 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -34,6 +34,7 @@ po/repeatticket.pot
 README
 static/css/repeat-ticket.css
 xt/cf.t
+xt/create_on_recurring_date.t
 xt/daily.t
 xt/end_conditions.t
 xt/monthly.t
diff --git a/META.yml b/META.yml
index 25e26e6..67fd818 100644
--- a/META.yml
+++ b/META.yml
@@ -27,6 +27,6 @@ requires:
   perl: 5.10.1
 resources:
   license: http://opensource.org/licenses/gpl-license.php
-version: '2.00'
+version: '2.01'
 x_module_install_rtx_version: '0.43'
 x_requires_rt: 5.0.0
diff --git a/README b/README
index d873773..7d6d59a 100644
--- a/README
+++ b/README
@@ -50,8 +50,36 @@ INSTALLATION
     Add bin/rt-repeat-ticket to the daily cron job.
     Restart your webserver
 
+MODES
+  Simple Mode VS Concurrent Tickets Mode
+    This extension supports two different modes for the repeat ticket
+    configurations. The extension originally only supported Concurrent
+    Tickets Mode but many users found the logic counter intuitive.
+
+    Any existing repeat ticket configurations from previous versions will be
+    in Concurrent Tickets Mode unless the definition is changed.
+
+    The default for new repeat ticket configurations is Simple Mode.
+
+   Simple Mode
+    In this mode tickets are created and start on the recurring date. If the
+    lead time field is filled out the ticket will be due that many days
+    after the recurring date. There is no check for existing active tickets
+    and if the rt-repeat-ticket script is run multiple times for the same
+    day it will create a new ticket for each run.
+
+   Concurrent Tickets Mode
+    In this mode the tickets are created with the due date as the recurring
+    date. The tickets start on the due date minus the lead time. You can
+    specify the max number of concurrent active tickets. If the
+    rt-repeat-ticket script is run multiple times for the same day it will
+    only create new tickets if there are fewer active tickets than the max
+    number of concurrent active tickets.
+
 CONFIGURATION
   $RepeatTicketCoexistentNumber
+    Only used in Concurrent Tickets Mode.
+
     The $RepeatTicketCoexistentNumber determines how many tickets can be in
     an active status for a recurrence at any time. A value of 1 means one
     ticket at a time can be active. New tickets will not be created until
@@ -60,16 +88,25 @@ CONFIGURATION
     config value. The extension default is 1 ticket.
 
   $RepeatTicketLeadTime
-    The $RepeatTicketLeadTime becomes the ticket Starts value and sets how
-    far in advance of a ticket's Due date you want the ticket to be created.
-    This essentially is how long you want to give people to work on the
-    ticket.
+    When in Simple Mode the $RepeatTicketLeadTime is the number of days to
+    add to the recurring date for the Due date of the ticket.
+
+    When in Concurrent Tickets Mode the $RepeatTicketLeadTime becomes the
+    ticket Starts value and sets how far in advance of a ticket's Due date
+    you want the ticket to be created. This essentially is how long you want
+    to give people to work on the ticket.
 
     For example, if you create a weekly recurrence scheduled on Mondays and
     set the lead time to 7 days, each Monday a ticket will be created with
     the Starts date set to that Monday and a Due date of the following
     Monday.
 
+    When in Concurrent Tickets Mode, with a number of concurrent active
+    tickets greater than 1, if you set the lead time to be larger than the
+    interval between recurring tickets it can result in strange behavior. It
+    is recommended that the ticket lead time be smaller or equal to the
+    interval between tickets.
+
     The value you set in RT_SiteConfig.pm becomes the system default, but
     you can set this value on each ticket as well. The extension default is
     14 days.
@@ -95,6 +132,16 @@ CONFIGURATION
     columns available in the Display Columns portlet on the RT ticket search
     page.
 
+  $RepeatTicketPreviewNumber
+    By default, the Recurrence Preview will show the next 5 tickets that
+    will be created. You can modify the number of tickets to show by setting
+    the $RepeatTicketPreviewNumber option:
+
+        Set($RepeatTicketPreviewNumber, 10);
+
+    Set the $RepeatTicketPreviewNumber option to 0 to hide the Recurrence
+    Preview.
+
   rt-repeat-ticket
     The rt-repeat-ticket utility evaluates all of your repeating tickets and
     creates any new tickets that are needed. With no parameters, it runs for
@@ -109,6 +156,11 @@ CONFIGURATION
     the future which might be handy if you want to experiment with
     recurrences in a test environment.
 
+   WARNING
+    If you run the script multiple times for the same day then it is
+    possible multiple tickets will be created for the same repeat ticket
+    configuration.
+
 USAGE
   Initial Tickets
     The initial ticket you create for a recurrence stores the schedule and
@@ -148,11 +200,13 @@ FAQ
 
         *   Do you have rt-repeat-tickets scheduled in cron? Is it running?
 
-        *   Do you have previous tickets still in an active state? Resolve
-            those tickets or increase the concurrent active tickets value.
+        *   If the repeat configuration is in Concurrent Tickets Mode do you
+            have previous tickets still in an active state? Resolve those
+            tickets or increase the concurrent active tickets value.
 
-        *   Is it the right day? Remember to subtract the lead time value to
-            determine the day new tickets should be created.
+        *   Is it the right day? If the repeat configuration is in
+            Concurrent Tickets Mode remember to subtract the lead time value
+            to determine the day new tickets should be created.
 
         *   If you set a start date and another criteria like day of the
             week, the new ticket will be created on the first time that day
@@ -167,6 +221,15 @@ FAQ
         Make sure those users have "SeeCustomField" and "ModifyCusotmField"
         rights granted for "Original Ticket" custom field.
 
+SEARCHING
+    To search for tickets that have recurrence enabled use the following in
+    a Ticket Search:
+
+        HasAttribute = 'RepeatTicketSettings'
+
+    This will need to be added on the Advanced tab so build the rest of your
+    search as desired and then add the clause on the Advanced tab.
+
 METHODS
   Run( RT::Attribute $attr, DateTime $checkday )
     Repeat the ticket if $checkday meets the repeat settings. It also tries
diff --git a/lib/RT/Extension/RepeatTicket.pm b/lib/RT/Extension/RepeatTicket.pm
index e90b3af..b9172a9 100644
--- a/lib/RT/Extension/RepeatTicket.pm
+++ b/lib/RT/Extension/RepeatTicket.pm
@@ -3,7 +3,7 @@ use strict;
 
 package RT::Extension::RepeatTicket;
 
-our $VERSION = "2.00";
+our $VERSION = "2.01";
 
 use RT::Interface::Web;
 use DateTime;

commit e47ede021358764389cc7e420cfcd8b9f874eee6
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Sep 20 09:25:40 2023 -0400

    Update M:I

diff --git a/META.yml b/META.yml
index eafebb8..25e26e6 100644
--- a/META.yml
+++ b/META.yml
@@ -8,7 +8,7 @@ configure_requires:
   ExtUtils::MakeMaker: 6.59
 distribution_type: module
 dynamic_config: 1
-generated_by: 'Module::Install version 1.19'
+generated_by: 'Module::Install version 1.21'
 license: gpl
 meta-spec:
   url: http://module-build.sourceforge.net/META-spec-v1.4.html
@@ -28,5 +28,5 @@ requires:
 resources:
   license: http://opensource.org/licenses/gpl-license.php
 version: '2.00'
-x_module_install_rtx_version: '0.42'
+x_module_install_rtx_version: '0.43'
 x_requires_rt: 5.0.0
diff --git a/inc/Module/AutoInstall.pm b/inc/Module/AutoInstall.pm
index 8852e0b..b977d4d 100644
--- a/inc/Module/AutoInstall.pm
+++ b/inc/Module/AutoInstall.pm
@@ -8,7 +8,7 @@ use ExtUtils::MakeMaker ();
 
 use vars qw{$VERSION};
 BEGIN {
-	$VERSION = '1.19';
+	$VERSION = '1.21';
 }
 
 # special map on pre-defined feature sets
diff --git a/inc/Module/Install.pm b/inc/Module/Install.pm
index 7ba98c2..3dd721b 100644
--- a/inc/Module/Install.pm
+++ b/inc/Module/Install.pm
@@ -31,7 +31,7 @@ BEGIN {
 	# This is not enforced yet, but will be some time in the next few
 	# releases once we can make sure it won't clash with custom
 	# Module::Install extensions.
-	$VERSION = '1.19';
+	$VERSION = '1.21';
 
 	# Storage for the pseudo-singleton
 	$MAIN    = undef;
diff --git a/inc/Module/Install/AutoInstall.pm b/inc/Module/Install/AutoInstall.pm
index 0e3dada..b771ef6 100644
--- a/inc/Module/Install/AutoInstall.pm
+++ b/inc/Module/Install/AutoInstall.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.19';
+	$VERSION = '1.21';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/Base.pm b/inc/Module/Install/Base.pm
index 9fa42c2..67ce900 100644
--- a/inc/Module/Install/Base.pm
+++ b/inc/Module/Install/Base.pm
@@ -4,7 +4,7 @@ package Module::Install::Base;
 use strict 'vars';
 use vars qw{$VERSION};
 BEGIN {
-	$VERSION = '1.19';
+	$VERSION = '1.21';
 }
 
 # Suspend handler for "redefined" warnings
diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm
index d65c753..93fc4f9 100644
--- a/inc/Module/Install/Can.pm
+++ b/inc/Module/Install/Can.pm
@@ -8,7 +8,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.19';
+	$VERSION = '1.21';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm
index 3072b08..3c9390a 100644
--- a/inc/Module/Install/Fetch.pm
+++ b/inc/Module/Install/Fetch.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.19';
+	$VERSION = '1.21';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/Include.pm b/inc/Module/Install/Include.pm
index 13fdcd0..b9b926f 100644
--- a/inc/Module/Install/Include.pm
+++ b/inc/Module/Install/Include.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.19';
+	$VERSION = '1.21';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/Makefile.pm b/inc/Module/Install/Makefile.pm
index 13a4464..1e214a0 100644
--- a/inc/Module/Install/Makefile.pm
+++ b/inc/Module/Install/Makefile.pm
@@ -8,7 +8,7 @@ use Fcntl qw/:flock :seek/;
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.19';
+	$VERSION = '1.21';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/Metadata.pm b/inc/Module/Install/Metadata.pm
index 11bf971..2ae8036 100644
--- a/inc/Module/Install/Metadata.pm
+++ b/inc/Module/Install/Metadata.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.19';
+	$VERSION = '1.21';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
@@ -455,12 +455,8 @@ sub author_from {
 my %license_urls = (
     perl         => 'http://dev.perl.org/licenses/',
     apache       => 'http://apache.org/licenses/LICENSE-2.0',
-    apache_1_1   => 'http://apache.org/licenses/LICENSE-1.1',
     artistic     => 'http://opensource.org/licenses/artistic-license.php',
-    artistic_2   => 'http://opensource.org/licenses/artistic-license-2.0.php',
     lgpl         => 'http://opensource.org/licenses/lgpl-license.php',
-    lgpl2        => 'http://opensource.org/licenses/lgpl-2.1.php',
-    lgpl3        => 'http://opensource.org/licenses/lgpl-3.0.html',
     bsd          => 'http://opensource.org/licenses/bsd-license.php',
     gpl          => 'http://opensource.org/licenses/gpl-license.php',
     gpl2         => 'http://opensource.org/licenses/gpl-2.0.php',
@@ -471,6 +467,12 @@ my %license_urls = (
     unrestricted => undef,
     restrictive  => undef,
     unknown      => undef,
+
+    # these are not actually allowed in meta-spec v1.4 but are left here for compatibility:
+    apache_1_1   => 'http://apache.org/licenses/LICENSE-1.1',
+    artistic_2   => 'http://opensource.org/licenses/artistic-license-2.0.php',
+    lgpl2        => 'http://opensource.org/licenses/lgpl-2.1.php',
+    lgpl3        => 'http://opensource.org/licenses/lgpl-3.0.html',
 );
 
 sub license {
diff --git a/inc/Module/Install/RTx.pm b/inc/Module/Install/RTx.pm
index 2dd9489..2889ece 100644
--- a/inc/Module/Install/RTx.pm
+++ b/inc/Module/Install/RTx.pm
@@ -9,7 +9,7 @@ no warnings 'once';
 use Term::ANSIColor qw(:constants);
 use Module::Install::Base;
 use base 'Module::Install::Base';
-our $VERSION = '0.42';
+our $VERSION = '0.43';
 
 use FindBin;
 use File::Glob     ();
@@ -134,7 +134,7 @@ lexicons ::
     if( $extra_args->{'remove_files'} ){
         $self->include('Module::Install::RTx::Remove');
         our @remove_files;
-        eval { require "etc/upgrade/remove_files" }
+        eval { require "./etc/upgrade/remove_files" }
           or print "No remove file located, no files to remove\n";
         $remove_files = join ",", map {"q(\$(DESTDIR)$plugin_path/$name/$_)"} @remove_files;
     }
diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm
index f7aa615..b6c1d37 100644
--- a/inc/Module/Install/Win32.pm
+++ b/inc/Module/Install/Win32.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.19';
+	$VERSION = '1.21';
 	@ISA     = 'Module::Install::Base';
 	$ISCORE  = 1;
 }
diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm
index 2db861a..d87eb9a 100644
--- a/inc/Module/Install/WriteAll.pm
+++ b/inc/Module/Install/WriteAll.pm
@@ -6,7 +6,7 @@ use Module::Install::Base ();
 
 use vars qw{$VERSION @ISA $ISCORE};
 BEGIN {
-	$VERSION = '1.19';
+	$VERSION = '1.21';
 	@ISA     = qw{Module::Install::Base};
 	$ISCORE  = 1;
 }
diff --git a/inc/YAML/Tiny.pm b/inc/YAML/Tiny.pm
index fb157a6..db3ae5c 100644
--- a/inc/YAML/Tiny.pm
+++ b/inc/YAML/Tiny.pm
@@ -2,12 +2,12 @@
 use 5.008001; # sane UTF-8 support
 use strict;
 use warnings;
-package YAML::Tiny; # git description: v1.72-7-g8682f63
+package YAML::Tiny; # git description: v1.73-12-ge02f827
 # XXX-INGY is 5.8.1 too old/broken for utf8?
 # XXX-XDG Lancaster consensus was that it was sufficient until
 # proven otherwise
 
-our $VERSION = '1.73';
+our $VERSION = '1.74';
 
 #####################################################################
 # The YAML::Tiny API.

commit 3b974aabb6109740e30fd33fa15b6ebdef9b739d
Merge: e11a412 2c0b210
Author: sunnavy <sunnavy at bestpractical.com>
Date:   Wed Sep 20 10:35:24 2023 -0400

    Merge branch 'add-mode-create-ticket-on-recurring-date'


commit 2c0b210aa9022ed6123121fdfb346493ab90f8aa
Author: Brad Embree <brad at bestpractical.com>
Date:   Sun Aug 20 19:52:12 2023 -0700

    Add/update tests for new Simple mode
    
    Simple mode will be the default which means the existing tests need to
    specify Concurrent Tickets Mode and the coexistent setting.

diff --git a/xt/cf.t b/xt/cf.t
index 7bb9ab0..8fb2e2c 100644
--- a/xt/cf.t
+++ b/xt/cf.t
@@ -33,6 +33,7 @@ $m->submit_form_ok(
             'repeat-type'                                          => 'daily',
             'repeat-details-daily'                                 => 'day',
             'repeat-details-daily-day'                             => 1,
+            'repeat-create-on-recurring-date'                      => 0,
         },
         button => 'SubmitTicket',
     },
diff --git a/xt/create_on_recurring_date.t b/xt/create_on_recurring_date.t
new file mode 100644
index 0000000..d34fd4b
--- /dev/null
+++ b/xt/create_on_recurring_date.t
@@ -0,0 +1,109 @@
+use strict;
+use warnings;
+
+use RT::Extension::RepeatTicket::Test tests => undef;
+
+use_ok('RT::Extension::RepeatTicket');
+require_ok('bin/rt-repeat-ticket');
+
+my ( $baseurl, $m ) = RT::Test->started_ok();
+{
+    diag "Run with repeat-create-on-recurring-date value of 1 so repeat-coexistent-number is 0";
+    my $daily_id = run_tests($baseurl, $m);
+
+    # A new ticket should be created for any day recurrence script runs for after start date of today
+    my $today = DateTime->now;
+    my $next_id = $daily_id + 1;
+    ok(!(RT::Repeat::Ticket::Run->run()), 'Ran recurrence script for today.');
+    my $ticket = RT::Ticket->new(RT->SystemUser);
+    ok(!($ticket->Load($next_id)), "No ticket created for today");
+
+    my $tomorrow = DateTime->now->add( days => 1 );
+    ok(!(RT::Repeat::Ticket::Run->run('-date=' . $tomorrow->ymd)), 'Ran recurrence script for tomorrow.');
+    ok($m->goto_ticket($next_id), "Recurrence ticket $next_id created for tomorrow.");
+    $m->text_like( qr/Set up recurring aperture maintenance/);
+    $ticket = RT::Ticket->new(RT->SystemUser);
+    ok($ticket->Load($next_id), "Loaded ticket $next_id");
+    is($ticket->StartsObj->ISO(Time => 0), $tomorrow->ymd, 'Starts tomorrow');
+    $tomorrow->add( days => 3 );
+    is($ticket->DueObj->ISO(Time => 0), $tomorrow->ymd, 'Due 3 days from tomorrow');
+
+    my $three_months = DateTime->now->add( months => 3 );
+    $next_id = $next_id + 1;
+    ok(!(RT::Repeat::Ticket::Run->run('-date=' . $three_months->ymd)), 'Ran recurrence script for 3 months from now.');
+    ok($m->goto_ticket($next_id), "Recurrence ticket $next_id created for 3 months from now.");
+    $m->text_like( qr/Set up recurring aperture maintenance/);
+    $ticket = RT::Ticket->new(RT->SystemUser);
+    ok($ticket->Load($next_id), "Loaded ticket $next_id");
+    is($ticket->StartsObj->ISO(Time => 0), $three_months->ymd, 'Starts 3 months from now');
+    $three_months->add( days => 3 );
+    is($ticket->DueObj->ISO(Time => 0), $three_months->ymd, 'Due 3 days from 3 months from now');
+
+    # a new ticket should be created even if there are existing tickets on the same day
+    $tomorrow = DateTime->now->add( days => 1 );
+    $next_id = $next_id + 1;
+    ok(!(RT::Repeat::Ticket::Run->run('-date=' . $tomorrow->ymd)), 'Ran recurrence script for tomorrow.');
+    ok($m->goto_ticket($next_id), "Recurrence ticket $next_id created for tomorrow.");
+    $m->text_like( qr/Set up recurring aperture maintenance/);
+    $ticket = RT::Ticket->new(RT->SystemUser);
+    ok($ticket->Load($next_id), "Loaded ticket $next_id");
+    is($ticket->StartsObj->ISO(Time => 0), $tomorrow->ymd, 'Starts tomorrow');
+    $tomorrow->add( days => 3 );
+    is($ticket->DueObj->ISO(Time => 0), $tomorrow->ymd, 'Due 3 days from tomorrow');
+
+    $three_months = DateTime->now->add( months => 3 );
+    $next_id = $next_id + 1;
+    ok(!(RT::Repeat::Ticket::Run->run('-date=' . $three_months->ymd)), 'Ran recurrence script for 3 months from now.');
+    ok($m->goto_ticket($next_id), "Recurrence ticket $next_id created for 3 months from now.");
+    $m->text_like( qr/Set up recurring aperture maintenance/);
+    $ticket = RT::Ticket->new(RT->SystemUser);
+    ok($ticket->Load($next_id), "Loaded ticket $next_id");
+    is($ticket->StartsObj->ISO(Time => 0), $three_months->ymd, 'Starts 3 months from now');
+    $three_months->add( days => 3 );
+    is($ticket->DueObj->ISO(Time => 0), $three_months->ymd, 'Due 3 days from 3 months from now');
+}
+
+sub run_tests{
+    my ($baseurl, $m) = @_;
+
+    ok( $m->login( 'root', 'password' ), 'logged in' );
+
+    $m->submit_form_ok( { form_name => 'CreateTicketInQueue', }, 'Click to create ticket' );
+
+    $m->content_contains('Enable Recurrence');
+
+    diag "Create a ticket with a recurrence in the General queue.";
+
+    $m->submit_form_ok(
+        {   form_name => 'TicketCreate',
+            fields    => {
+                'Subject'                         => 'Set up recurring aperture maintenance',
+                'Content'                         => 'Perform work on portals once per day M - F',
+                'repeat-enabled'                  => 1,
+                'repeat-type'                     => 'daily',
+                'repeat-details-daily'            => 'day',
+                'repeat-details-daily-day'        => 1,
+                'repeat-create-on-recurring-date' => 1,
+                'repeat-coexistent-number'        => 0,
+                'repeat-lead-time'                => 3,
+                'repeat-start-date'               => DateTime->now->ymd,
+            },
+            button => 'SubmitTicket',
+        },
+        'Create'
+    );
+
+    $m->text_like( qr/Ticket\s(\d+)\screated in queue/);
+
+    my ($daily_id) = $m->content =~ /Ticket\s(\d+)\screated in queue/;
+    ok($daily_id, "Created ticket with id: $daily_id");
+
+    # resolving the parent ticket should have no affect on creating new tickets
+    my $ticket = RT::Ticket->new(RT->SystemUser);
+    ok($ticket->Load($daily_id), "Loaded ticket $daily_id");
+    ok($ticket->SetStatus('resolved'), "Ticket $daily_id resolved");
+
+    return $daily_id;
+}
+
+done_testing;
diff --git a/xt/daily.t b/xt/daily.t
index e220327..73d81cc 100644
--- a/xt/daily.t
+++ b/xt/daily.t
@@ -88,12 +88,14 @@ sub run_tests{
     $m->submit_form_ok(
         {   form_name => 'TicketCreate',
             fields    => {
-                'Subject'                  => 'Set up recurring aperture maintenance',
-                'Content'                  => 'Perform work on portals once per day',
-                'repeat-enabled'           => 1,
-                'repeat-type'              => 'daily',
-                'repeat-details-daily'     => 'day',
-                'repeat-details-daily-day' => 1,
+                'Subject'                         => 'Set up recurring aperture maintenance',
+                'Content'                         => 'Perform work on portals once per day',
+                'repeat-enabled'                  => 1,
+                'repeat-type'                     => 'daily',
+                'repeat-details-daily'            => 'day',
+                'repeat-details-daily-day'        => 1,
+                'repeat-create-on-recurring-date' => 0,
+                'repeat-coexistent-number'        => 1,
             },
             button => 'SubmitTicket',
         },
diff --git a/xt/end_conditions.t b/xt/end_conditions.t
index 7f66a7a..8cd34ba 100644
--- a/xt/end_conditions.t
+++ b/xt/end_conditions.t
@@ -110,12 +110,14 @@ sub run_tests{
     $m->submit_form_ok(
         {   form_name => 'TicketCreate',
             fields    => {
-                'Subject'                  => 'Set up recurring aperture maintenance',
-                'Content'                  => 'Perform work on portals once per day',
-                'repeat-enabled'           => 1,
-                'repeat-type'              => 'daily',
-                'repeat-details-daily'     => 'day',
-                'repeat-details-daily-day' => 1,
+                'Subject'                         => 'Set up recurring aperture maintenance',
+                'Content'                         => 'Perform work on portals once per day',
+                'repeat-enabled'                  => 1,
+                'repeat-type'                     => 'daily',
+                'repeat-details-daily'            => 'day',
+                'repeat-details-daily-day'        => 1,
+                'repeat-create-on-recurring-date' => 0,
+                'repeat-coexistent-number'        => 1,
             },
             button => 'SubmitTicket',
         },
diff --git a/xt/monthly.t b/xt/monthly.t
index be27f1b..e501b04 100644
--- a/xt/monthly.t
+++ b/xt/monthly.t
@@ -30,6 +30,7 @@ $m->submit_form_ok(
             'repeat-type'                      => 'monthly',
             'repeat-details-monthly-day-day'   => $day->day,
             'repeat-details-monthly-day-month' => 1,
+            'repeat-create-on-recurring-date'  => 0,
         },
         button => 'SubmitTicket',
     },
diff --git a/xt/on_complete.t b/xt/on_complete.t
index cd97521..c6de3e5 100644
--- a/xt/on_complete.t
+++ b/xt/on_complete.t
@@ -26,6 +26,8 @@ $m->submit_form_ok(
             'repeat-type'                     => 'monthly',
             'repeat-details-monthly'          => 'complete',
             'repeat-details-monthly-complete' => 0,
+            'repeat-create-on-recurring-date' => 0,
+            'repeat-coexistent-number'        => 1,
         },
         button => 'SubmitTicket',
     },
diff --git a/xt/start_date.t b/xt/start_date.t
index c85620d..f6e21b0 100644
--- a/xt/start_date.t
+++ b/xt/start_date.t
@@ -22,16 +22,17 @@ diag "Repeat start date is: " . $day->ymd;
 $m->submit_form_ok(
     {   form_name => 'TicketCreate',
         fields    => {
-            'Subject'                     => 'Set up recurring aperture maintenance',
-            'Content'                     => 'Perform work on portals on Thursday',
-            'repeat-lead-time'            => 7,
-            'repeat-coexistent-number'    => 2,
-            'repeat-enabled'              => 1,
-            'repeat-type'                 => 'weekly',
-            'repeat-details-weekly'       => 'week',
-            'repeat-details-weekly-week'  => 1,
-            'repeat-details-weekly-weeks' => 'th',
-            'repeat-start-date'           => $day->ymd,
+            'Subject'                         => 'Set up recurring aperture maintenance',
+            'Content'                         => 'Perform work on portals on Thursday',
+            'repeat-lead-time'                => 7,
+            'repeat-coexistent-number'        => 2,
+            'repeat-enabled'                  => 1,
+            'repeat-type'                     => 'weekly',
+            'repeat-details-weekly'           => 'week',
+            'repeat-details-weekly-week'      => 1,
+            'repeat-details-weekly-weeks'     => 'th',
+            'repeat-start-date'               => $day->ymd,
+            'repeat-create-on-recurring-date' => 0,
         },
         button => 'SubmitTicket',
     },
diff --git a/xt/weekly.t b/xt/weekly.t
index 51790db..6ff095d 100644
--- a/xt/weekly.t
+++ b/xt/weekly.t
@@ -1,7 +1,7 @@
 use strict;
 use warnings;
 
-use RT::Extension::RepeatTicket::Test tests => 26;
+use RT::Extension::RepeatTicket::Test tests => 31;
 
 use_ok('RT::Extension::RepeatTicket');
 require_ok('bin/rt-repeat-ticket');
@@ -19,15 +19,16 @@ diag "Create a ticket with a recurrence in the General queue.";
 $m->submit_form_ok(
     {   form_name => 'TicketCreate',
         fields    => {
-            'Subject'                     => 'Set up recurring aperture maintenance',
-            'Content'                     => 'Perform work on portals on Tuesday and Thursday',
-            'repeat-lead-time'            => 7,
-            'repeat-coexistent-number'    => 2,
-            'repeat-enabled'              => 1,
-            'repeat-type'                 => 'weekly',
-            'repeat-details-weekly'       => 'week',
-            'repeat-details-weekly-week'  => 1,
-            'repeat-details-weekly-weeks' => 'th',
+            'Subject'                         => 'Set up recurring aperture maintenance',
+            'Content'                         => 'Perform work on portals on Tuesday and Thursday',
+            'repeat-lead-time'                => 7,
+            'repeat-coexistent-number'        => 2,
+            'repeat-enabled'                  => 1,
+            'repeat-type'                     => 'weekly',
+            'repeat-details-weekly'           => 'week',
+            'repeat-details-weekly-week'      => 1,
+            'repeat-details-weekly-weeks'     => 'th',
+            'repeat-create-on-recurring-date' => 0,
         },
         button => 'SubmitTicket',
     },
@@ -74,6 +75,18 @@ ok(!(RT::Repeat::Ticket::Run->run('-date=' . $thurs->ymd)), 'Ran recurrence scri
 my $ticket4 = RT::Ticket->new(RT->SystemUser);
 ok(!($ticket4->Load($third + 1)), 'No fourth ticket created.');
 
+diag "Run after changing create-on-recurring-date to 1";
+# change to create on recurring date and then a 4th ticket should be created
+ok( $m->goto_ticket($weekly_id), "Found ticket $weekly_id.");
+$m->follow_link_ok( {text => 'Recurrence'}, 'Loaded recurrence edit' );
+
+$m->form_name("ModifyRecurrence");
+$m->field('repeat-create-on-recurring-date' => 1);
+$m->click_button(name => 'SubmitTicket');
+$m->text_like( qr/Recurrence updated/);
+
+ok(!(RT::Repeat::Ticket::Run->run('-date=' . $thurs->ymd)), 'Ran recurrence script for next Thursday again.');
+ok($ticket4->Load($third + 1), 'Fourth ticket created after changing create-on-recurring-date to 1.');
 
 # Didn't want to add DateTime::Format::Natural as a dependency.
 sub GetThursday {
diff --git a/xt/yearly.t b/xt/yearly.t
index 0d65f6a..c6359b7 100644
--- a/xt/yearly.t
+++ b/xt/yearly.t
@@ -28,6 +28,7 @@ $m->submit_form_ok(
             'repeat-type'                     => 'yearly',
             'repeat-details-yearly-day-month' => $day->month,
             'repeat-details-yearly-day-day'   => $day->day,
+            'repeat-create-on-recurring-date' => 0,
         },
         button => 'SubmitTicket',
     },

commit dc61451e68942e67e1c303598a7c8088f280167c
Author: Brad Embree <brad at bestpractical.com>
Date:   Mon Aug 28 13:52:24 2023 -0700

    Add repeat ticket preview mode
    
    Add a preview mode to the repeat ticket logic to use in the RT UI.
    
    If preview mode is set then the Create method returns an array of start
    and due dates for each ticket that would have been created instead of an
    array of ticket ids.

diff --git a/html/Ticket/Elements/EditRecurrence b/html/Ticket/Elements/EditRecurrence
index 394d9a5..e71f23b 100644
--- a/html/Ticket/Elements/EditRecurrence
+++ b/html/Ticket/Elements/EditRecurrence
@@ -265,6 +265,35 @@ jQuery( function () {
       </div>
     </div>
   </div>
+
+  <div class="<% $ARGSRef->{'repeat-enabled'} && $ReadOnly && $preview_num ? '' : 'hidden' %>" id="tickets-preview">
+    <&| /Widgets/TitleBox, title => loc("Recurrence Preview") &>
+      <div class="form-row" style="text-align:left;">
+        <p>This shows the next <% $preview_num %> tickets that would be created with the current recurrence configuration.</p>
+
+        <p>If the recurrence configuration uses the concurrent active tickets option or the create new task after task is complete option the preview may not be accurate as it depends on whether previously created tickets are still active.</p>
+      </div>
+      <div class="form-row" style="text-align:center;">
+        <div class="col-6">
+          <span><% loc('Starts') . ( $ARGSRef->{'repeat-create-on-recurring-date'} ? ' - ' . loc('Recurring Date') : '' ) %></span>
+        </div>
+        <div class="col-6">
+          <span><% loc('Due') . ( $ARGSRef->{'repeat-create-on-recurring-date'} ? '' : ' - ' . loc('Recurring Date') ) %></span>
+        </div>
+      </div>
+% foreach my $preview ( @preview_tickets ) {
+      <div class="form-row" style="text-align:center;">
+        <div class="col-6">
+          <span><% $preview->[0] %></span>
+        </div>
+        <div class="col-6">
+          <span><% $preview->[1] %></span>
+        </div>
+      </div>
+% }
+    </&>
+    </div>
+
 </div>
 
 <%init>
@@ -276,8 +305,9 @@ my @month_labels = qw(January February March April  May June July August Septemb
 my $lead_time_tooltip_simple     = loc('Ticket will be due this many days after the recurring date.');
 my $lead_time_tooltip_concurrent = loc('Ticket will be created this many days before the recurring date.');
 
+my $repeat;
 if ( $Ticket ) {
-    my ($repeat) = $Ticket->Attributes->Named('RepeatTicketSettings');
+    ($repeat) = $Ticket->Attributes->Named('RepeatTicketSettings');
     if ( $repeat ) {
         $ARGSRef = $repeat->Content if $repeat;
     }
@@ -300,8 +330,8 @@ if ( $ARGSRef->{'repeat-create-on-recurring-date'} ) {
     $ARGSRef->{'repeat-coexistent-number'} = 0;
 }
 else {
-    # 0 is a valid value, so check for defined before setting to default
-    if ( not defined $ARGSRef->{'repeat-coexistent-number'} ){
+    # 0 is not a valid value for concurrent tickets mode
+    if ( not $ARGSRef->{'repeat-coexistent-number'} ){
         $ARGSRef->{'repeat-coexistent-number'} = RT->Config->Get('RepeatTicketCoexistentNumber') || 1;
     }
 }
@@ -354,6 +384,70 @@ my $month = sub {
     $str .= "</select>";
     return $str;
 };
+
+my @preview_tickets;
+my $preview_num = RT->Config->Get('RepeatTicketPreviewNumber') // 5;
+unless ( $Initial ) {
+  # find next X tickets to be created for preview window
+    if ( $preview_num ) {
+        RT::Extension::RepeatTicket::RepeatTicketPreview(1);
+
+        my $content = $repeat->Content;
+
+        my ( $last_due, $last_created );
+        if ( $content->{'last-ticket'} ) {
+            my $last_ticket = RT::Ticket->new( RT->SystemUser );
+            $last_ticket->Load( $content->{'last-ticket'} );
+
+            if ( $last_ticket->DueObj->Unix ) {
+                $last_due = DateTime->from_epoch(
+                    epoch     => $last_ticket->DueObj->Unix,
+                    time_zone => RT->Config->Get('Timezone'),
+                );
+                $last_due->truncate( to => 'day' );
+            }
+
+            $last_created = DateTime->from_epoch(
+                epoch     => $last_ticket->CreatedObj->Unix,
+                time_zone => RT->Config->Get('Timezone'),
+            );
+            $last_created->truncate( to => 'day' );
+        }
+        my $today = DateTime->today( time_zone => RT->Config->Get('Timezone') );
+        $today->truncate( to => 'day' );
+
+        my $start_set_date;
+        if ( $content->{'repeat-create-on-recurring-date'} ) {
+            $start_set_date = $last_created || $today;
+        }
+        else {
+            $start_set_date = $last_due || $last_created || $today;
+        }
+
+        my $set = RT::Extension::RepeatTicket::BuildSet( $content, $start_set_date );
+        if ($set) {
+            # use a counter to ensure we don't get an endless loop trying to find the next X tickets
+            my $counter = 0;
+            my $iter = $set->iterator;
+            while ( my $dt = $iter->next ) {
+                if ( !$content->{'repeat-create-on-recurring-date'} && $content->{'repeat-lead-time'} ) {
+                    # the date to pass to Repeat must be the start/create date which in concurrent mode is lead time days in the past
+                    $dt->add( days => -1 * $content->{'repeat-lead-time'} );
+                }
+
+                # skip dates in the past
+                next if $dt->epoch < $today->epoch;
+
+                push @preview_tickets, RT::Extension::RepeatTicket::Repeat( $repeat, $dt );
+                $counter++;
+
+                last if scalar @preview_tickets >= $preview_num || $counter > $preview_num * 2;
+            }
+        }
+
+        RT::Extension::RepeatTicket::RepeatTicketPreview(0);
+    }
+}
 </%init>
 <%args>
 $ARGSRef => undef
diff --git a/lib/RT/Extension/RepeatTicket.pm b/lib/RT/Extension/RepeatTicket.pm
index 0924b8e..e90b3af 100644
--- a/lib/RT/Extension/RepeatTicket.pm
+++ b/lib/RT/Extension/RepeatTicket.pm
@@ -163,6 +163,16 @@ sub Run {
     return @ids;
 }
 
+my $repeat_ticket_preview = 0;
+sub RepeatTicketPreview {
+    my $val = shift;
+
+    $repeat_ticket_preview = $val
+        if defined $val;
+
+    return $repeat_ticket_preview;
+}
+
 sub Repeat {
     my $attr      = shift;
     my @checkdays = @_;
@@ -175,7 +185,9 @@ sub Repeat {
 
     my $tickets_needed = TicketsToMeetCoexistentNumber($attr);
 
-    return unless $tickets_needed || $content->{'repeat-create-on-recurring-date'};
+    return unless $tickets_needed
+        || $content->{'repeat-create-on-recurring-date'}
+        || RepeatTicketPreview;
 
     for my $checkday (@checkdays) {
         # Adjust by lead time
@@ -446,31 +458,38 @@ sub Repeat {
             }
         }
 
-        my ( $id, $txn, $msg ) = _RepeatTicket(
-            $repeat_ticket,
-            Starts => $starts->ISO,
-            $due
-            ? ( Due => $due->ISO )
-            : (),
-        );
-
-        if ($id) {
-            $RT::Logger->info(
-                "Repeated ticket " . $repeat_ticket->id . ": $id" );
-            $content->{'repeat-occurrences'}++;
-            $content->{'last-ticket'} = $id;
-            push @{ $content->{'tickets'} }, $id;
-            push @ids, $id;
+        if ( RepeatTicketPreview ) {
+            # return the ticket starts and due date
+            push @ids, [ $starts->Date, $due ? $due->Date : () ];
         }
         else {
-            $RT::Logger->error( "Failed to repeat ticket for "
-                  . $repeat_ticket->id
-                  . ": $msg" );
-            next;
+            my ( $id, $txn, $msg ) = _RepeatTicket(
+                $repeat_ticket,
+                Starts => $starts->ISO,
+                $due
+                ? ( Due => $due->ISO )
+                : (),
+            );
+
+            if ($id) {
+                $RT::Logger->info(
+                    "Repeated ticket " . $repeat_ticket->id . ": $id" );
+                $content->{'repeat-occurrences'}++;
+                $content->{'last-ticket'} = $id;
+                push @{ $content->{'tickets'} }, $id;
+                push @ids, $id;
+            }
+            else {
+                $RT::Logger->error( "Failed to repeat ticket for "
+                    . $repeat_ticket->id
+                    . ": $msg" );
+                next;
+            }
         }
     }
 
-    $attr->SetContent($content);
+    $attr->SetContent($content)
+        unless RepeatTicketPreview;
     return @ids;
 }
 
@@ -653,7 +672,8 @@ sub MaybeRepeatMore {
     $last_created->truncate( to => 'day' );
 
     $content->{tickets} = GetActiveTickets($content);
-    $attr->SetContent($content);
+    $attr->SetContent($content)
+        unless RepeatTicketPreview;
 
     my @ids;
     if ( $tickets_needed ) {

commit f0977a3f58bf4da65f0a798234c5baea6eac6f84
Author: Brad Embree <brad at bestpractical.com>
Date:   Sun Aug 20 19:45:02 2023 -0700

    Add new simple mode
    
    Add repeat-create-on-recurring-date attribute which indicates simple
    mode for the repeat configuration. The recurring date indicates the date
    the ticket should be created.
    
    It allows for, and assumes, repeat-coexistent-number is 0.

diff --git a/html/Ticket/Elements/EditRecurrence b/html/Ticket/Elements/EditRecurrence
index 5e4c33c..394d9a5 100644
--- a/html/Ticket/Elements/EditRecurrence
+++ b/html/Ticket/Elements/EditRecurrence
@@ -1,4 +1,4 @@
-<script>
+<script type="text/javascript">
 jQuery( function () {
     jQuery('div.repeat input[name=repeat-enabled]').change( function () {
         var val = jQuery(this).val();
@@ -9,13 +9,31 @@ jQuery( function () {
             jQuery('div.repeat div.repeat-toggle').addClass('hidden');
         }
     } );
- 
+
     jQuery('div.repeat input[name=repeat-type]').change( function () {
         var val = jQuery(this).val();
         jQuery('div.repeat .repeat-details:not(.repeat-details-'+val+')').addClass('hidden');
         jQuery('div.repeat .repeat-details-' +val ).removeClass('hidden');
     } );
 
+    jQuery('div.repeat input[name=repeat-create-on-recurring-date]').change( function () {
+        var val = jQuery(this).val();
+        if ( val == 0 ) {
+            jQuery('div#repeat-coexistent-number-div').removeClass('hidden');
+            jQuery('svg#lead-time-tooltip-simple').addClass('hidden');
+            jQuery('svg#lead-time-tooltip-concurrent').removeClass('hidden');
+            if ( jQuery('div.repeat input[name=repeat-coexistent-number]').val() == 0 ) {
+              jQuery('div.repeat input[name=repeat-coexistent-number]').val( <% RT->Config->Get('RepeatTicketCoexistentNumber') || 1 %> );
+            }
+        }
+        else {
+            jQuery('div#repeat-coexistent-number-div').addClass('hidden');
+            jQuery('svg#lead-time-tooltip-simple').removeClass('hidden');
+            jQuery('svg#lead-time-tooltip-concurrent').addClass('hidden');
+            jQuery('div.repeat input[name=repeat-coexistent-number]').val(0);
+        }
+    } );
+
 % if ( $ReadOnly ) {
     jQuery('div.repeat.read-only input, div.repeat.read-only select').attr('disabled', true);
 % }
@@ -41,14 +59,26 @@ jQuery( function () {
 
     <div class="form-row">
       <div class="label input col-4">
-        <&|/l&>Ticket lead time (days)</&>:
+        <&|/l&>Ticket Create Mode</&>:
       </div>
       <div class="value col-8">
-        <input class="form-control" size="3" name="repeat-lead-time" type="text" value="<% $ARGSRef->{'repeat-lead-time'} %>" />
+        <div class="form-check form-check-inline">
+          <div class="custom-control custom-radio">
+            <input type="radio" id="<% $InputIdPrefix %>repeat-create-on-recurring-date-true" name="repeat-create-on-recurring-date" class="custom-control-input" value="1" <% $ARGSRef->{'repeat-create-on-recurring-date'} ? ' checked="checked"': '' |n %>>
+            <label class="custom-control-label" for="<% $InputIdPrefix %>repeat-create-on-recurring-date-true"><&|/l&>Simple Mode - Ticket created on Recurring Date</&></label>
+          </div>
+        </div>
+
+        <div class="form-check form-check-inline">
+          <div class="custom-control custom-radio">
+            <input type="radio" id="<% $InputIdPrefix %>repeat-create-on-recurring-date-false" name="repeat-create-on-recurring-date" class="custom-control-input" value="0" <% $ARGSRef->{'repeat-create-on-recurring-date'} ? '' : ' checked="checked"' |n %>>
+            <label class="custom-control-label" for="<% $InputIdPrefix %>repeat-create-on-recurring-date-false"><&|/l&>Concurrent Tickets Mode - Ticket due on Recurring Date</&></label>
+          </div>
+        </div>
       </div>
     </div>
 
-    <div class="form-row">
+    <div id="repeat-coexistent-number-div" class="form-row <% $ARGSRef->{'repeat-create-on-recurring-date'} ? 'hidden' : '' %>">
       <div class="label input col-4">
         <&|/l&>Concurrent active tickets</&>:
       </div>
@@ -57,6 +87,17 @@ jQuery( function () {
       </div>
     </div>
 
+    <div class="form-row">
+      <div class="label input col-4">
+        <&|/l&>Ticket lead time (days)</&>:
+        <span id="lead-time-tooltip-simple" class="far fa-question-circle icon-helper <% $ARGSRef->{'repeat-create-on-recurring-date'} ? '' : 'hidden' %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc($lead_time_tooltip_simple) %>"></span>
+        <span id="lead-time-tooltip-concurrent" class="far fa-question-circle icon-helper <% $ARGSRef->{'repeat-create-on-recurring-date'} ? 'hidden' : '' %>" data-toggle="tooltip" data-placement="top" data-original-title="<% loc($lead_time_tooltip_concurrent) %>"></span>
+      </div>
+      <div class="value col-8">
+        <input class="form-control" size="3" name="repeat-lead-time" type="text" value="<% $ARGSRef->{'repeat-lead-time'} %>" />
+      </div>
+    </div>
+
     <div class="form-row">
       <div class="label input col-4">
         <&|/l&>Recurrence pattern</&>:
@@ -232,6 +273,9 @@ my @week_values = qw(su mo tu we th fr sa);
 my @week_number_labels = qw(First Second Third Fourth Last); # loc_qw
 my @month_labels = qw(January February March April  May June July August September October November December); # loc_qw
 
+my $lead_time_tooltip_simple     = loc('Ticket will be due this many days after the recurring date.');
+my $lead_time_tooltip_concurrent = loc('Ticket will be created this many days before the recurring date.');
+
 if ( $Ticket ) {
     my ($repeat) = $Ticket->Attributes->Named('RepeatTicketSettings');
     if ( $repeat ) {
@@ -250,10 +294,16 @@ $ARGSRef->{'repeat-details-monthly'} ||= 'day';
 $ARGSRef->{'repeat-details-yearly'} ||= 'day';
 $ARGSRef->{'repeat-end'} ||= 'none';
 $ARGSRef->{'repeat-lead-time'} //= RT->Config->Get('RepeatTicketLeadTime') // 14;
+$ARGSRef->{'repeat-create-on-recurring-date'} //= 1;
 
-# 0 is a valid value, so check for defined before setting to default
-if ( not defined $ARGSRef->{'repeat-coexistent-number'} ){
-    $ARGSRef->{'repeat-coexistent-number'} = RT->Config->Get('RepeatTicketCoexistentNumber') || 1;
+if ( $ARGSRef->{'repeat-create-on-recurring-date'} ) {
+    $ARGSRef->{'repeat-coexistent-number'} = 0;
+}
+else {
+    # 0 is a valid value, so check for defined before setting to default
+    if ( not defined $ARGSRef->{'repeat-coexistent-number'} ){
+        $ARGSRef->{'repeat-coexistent-number'} = RT->Config->Get('RepeatTicketCoexistentNumber') || 1;
+    }
 }
 
 my $input = sub {
diff --git a/lib/RT/Extension/RepeatTicket.pm b/lib/RT/Extension/RepeatTicket.pm
index 2696c41..0924b8e 100644
--- a/lib/RT/Extension/RepeatTicket.pm
+++ b/lib/RT/Extension/RepeatTicket.pm
@@ -174,13 +174,19 @@ sub Repeat {
     my $repeat_ticket = $attr->Object;
 
     my $tickets_needed = TicketsToMeetCoexistentNumber($attr);
-    return unless $tickets_needed;
+
+    return unless $tickets_needed || $content->{'repeat-create-on-recurring-date'};
 
     for my $checkday (@checkdays) {
         # Adjust by lead time
         my $original_date = $checkday->clone();
-        $checkday = $checkday->add( days => $content->{'repeat-lead-time'} )
-          if defined $content->{'repeat-lead-time'};
+        if (
+            defined $content->{'repeat-lead-time'}
+            &&
+            ! $content->{'repeat-create-on-recurring-date'}
+        ) {
+            $checkday = $checkday->add( days => $content->{'repeat-lead-time'} );
+        }
         $RT::Logger->debug( 'Checking date ' . $original_date ->ymd .
                             ' with adjusted lead time date ' . $checkday->ymd );
 
@@ -238,7 +244,16 @@ sub Repeat {
             time_zone => RT->Config->Get('Timezone'),
         );
         $last_created->truncate( to => 'day' );
-        next unless $last_created->ymd lt $checkday->ymd;
+
+        # if we are in simple mode we do not care about the last ticket
+        # due date or create date
+        if ( $content->{'repeat-create-on-recurring-date'} ) {
+            # set last due to checkday so all sets start with the checkday
+            $last_due = $checkday;
+        }
+        else {
+            next unless $last_created->ymd lt $checkday->ymd;
+        }
 
         my $set;
         if ( $content->{'repeat-type'} eq 'daily' ) {
@@ -421,6 +436,14 @@ sub Repeat {
         if ($set) {
             $due = RT::Date->new( RT->SystemUser );
             $due->Set( Format => 'unknown', Value => $checkday );
+
+            if (
+                defined $content->{'repeat-lead-time'}
+                &&
+                $content->{'repeat-create-on-recurring-date'}
+            ) {
+                $due->AddDays( $content->{'repeat-lead-time'} );
+            }
         }
 
         my ( $id, $txn, $msg ) = _RepeatTicket(
@@ -455,7 +478,10 @@ sub TicketsToMeetCoexistentNumber {
     my $attr    = shift;
     my $content = $attr->Content;
 
-    my $co_number = $content->{'repeat-coexistent-number'};
+    my $co_number
+        = $content->{'repeat-create-on-recurring-date'}
+            ? 0
+            : $content->{'repeat-coexistent-number'};
     $co_number = RT->Config->Get('RepeatTicketCoexistentNumber')
       unless defined $co_number && length $co_number;  # respect 0 but ''
     return unless $co_number;
@@ -631,92 +657,7 @@ sub MaybeRepeatMore {
 
     my @ids;
     if ( $tickets_needed ) {
-        my $set;
-        if ( $content->{'repeat-type'} eq 'daily' ) {
-            if ( $content->{'repeat-details-daily'} eq 'day' ) {
-                $set = DateTime::Event::ICal->recur(
-                    dtstart  => $last_due || $last_created,
-                    freq     => 'daily',
-                    interval => $content->{'repeat-details-daily-day'} || 1,
-                );
-            }
-            elsif ( $content->{'repeat-details-daily'} eq 'weekday' ) {
-                $set = DateTime::Event::ICal->recur(
-                    dtstart  => $last_due || $last_created,
-                    freq    => 'daily',
-                    byday   => [ 'mo', 'tu', 'we', 'th', 'fr' ],
-                );
-            }
-        }
-        elsif ( $content->{'repeat-type'} eq 'weekly' ) {
-            if ( $content->{'repeat-details-weekly'} eq 'week' ) {
-                my $weeks = $content->{'repeat-details-weekly-weeks'};
-                if ( defined $weeks ) {
-                    $set = DateTime::Event::ICal->recur(
-                        dtstart  => $last_due || $last_created,
-                        freq     => 'weekly',
-                        interval => $content->{'repeat-details-weekly-week'}
-                          || 1,
-                        byday => ref $weeks ? $weeks : [$weeks],
-                    );
-                }
-                else {
-                    $RT::Logger->error('No weeks defined');
-                }
-            }
-        }
-        elsif ( $content->{'repeat-type'} eq 'monthly' ) {
-            if ( $content->{'repeat-details-monthly'} eq 'day' ) {
-                $set = DateTime::Event::ICal->recur(
-                    dtstart  => $last_due || $last_created,
-                    freq     => 'monthly',
-                    interval => $content->{'repeat-details-monthly-day-month'}
-                      || 1,
-                    bymonthday => $content->{'repeat-details-monthly-day-day'}
-                      || 1,
-                );
-            }
-            elsif ( $content->{'repeat-details-monthly'} eq 'week' ) {
-                my $number = $content->{'repeat-details-monthly-week-number'}
-                  || 1;
-                my $day = $content->{'repeat-details-monthly-week-week'}
-                  || 'mo';
-
-                $set = DateTime::Event::ICal->recur(
-                    dtstart  => $last_due || $last_created,
-                    freq     => 'monthly',
-                    interval => $content->{'repeat-details-monthly-week-month'}
-                      || 1,
-                    byday => $number . $day,
-                );
-            }
-        }
-        elsif ( $content->{'repeat-type'} eq 'yearly' ) {
-            if ( $content->{'repeat-details-yearly'} eq 'day' ) {
-                $set = DateTime::Event::ICal->recur(
-                    dtstart  => $last_due || $last_created,
-                    freq    => 'yearly',
-                    bymonth => $content->{'repeat-details-yearly-day-month'}
-                      || 1,
-                    bymonthday => $content->{'repeat-details-yearly-day-day'}
-                      || 1,
-                );
-            }
-            elsif ( $content->{'repeat-details-yearly'} eq 'week' ) {
-                my $number = $content->{'repeat-details-yearly-week-number'}
-                  || 1;
-                my $day = $content->{'repeat-details-yearly-week-week'} || 'mo';
-
-                $set = DateTime::Event::ICal->recur(
-                    dtstart  => $last_due || $last_created,
-                    freq    => 'yearly',
-                    bymonth => $content->{'repeat-details-yearly-week-month'}
-                      || 1,
-                    byday => $number . $day,
-                );
-            }
-        }
-
+        my $set = BuildSet( $content, $last_due, $last_created );
         if ($set) {
             my @dates;
             my $iter = $set->iterator;
@@ -735,6 +676,98 @@ sub MaybeRepeatMore {
     return @ids;
 }
 
+sub BuildSet {
+    my ( $content, $last_due, $last_created ) = @_;
+
+    my $set;
+    if ( $content->{'repeat-type'} eq 'daily' ) {
+        if ( $content->{'repeat-details-daily'} eq 'day' ) {
+            $set = DateTime::Event::ICal->recur(
+                dtstart  => $last_due || $last_created,
+                freq     => 'daily',
+                interval => $content->{'repeat-details-daily-day'} || 1,
+            );
+        }
+        elsif ( $content->{'repeat-details-daily'} eq 'weekday' ) {
+            $set = DateTime::Event::ICal->recur(
+                dtstart  => $last_due || $last_created,
+                freq    => 'daily',
+                byday   => [ 'mo', 'tu', 'we', 'th', 'fr' ],
+            );
+        }
+    }
+    elsif ( $content->{'repeat-type'} eq 'weekly' ) {
+        if ( $content->{'repeat-details-weekly'} eq 'week' ) {
+            my $weeks = $content->{'repeat-details-weekly-weeks'};
+            if ( defined $weeks ) {
+                $set = DateTime::Event::ICal->recur(
+                    dtstart  => $last_due || $last_created,
+                    freq     => 'weekly',
+                    interval => $content->{'repeat-details-weekly-week'}
+                        || 1,
+                    byday => ref $weeks ? $weeks : [$weeks],
+                );
+            }
+            else {
+                $RT::Logger->error('No weeks defined');
+            }
+        }
+    }
+    elsif ( $content->{'repeat-type'} eq 'monthly' ) {
+        if ( $content->{'repeat-details-monthly'} eq 'day' ) {
+            $set = DateTime::Event::ICal->recur(
+                dtstart  => $last_due || $last_created,
+                freq     => 'monthly',
+                interval => $content->{'repeat-details-monthly-day-month'}
+                    || 1,
+                bymonthday => $content->{'repeat-details-monthly-day-day'}
+                    || 1,
+            );
+        }
+        elsif ( $content->{'repeat-details-monthly'} eq 'week' ) {
+            my $number = $content->{'repeat-details-monthly-week-number'}
+                || 1;
+            my $day = $content->{'repeat-details-monthly-week-week'}
+                || 'mo';
+
+            $set = DateTime::Event::ICal->recur(
+                dtstart  => $last_due || $last_created,
+                freq     => 'monthly',
+                interval => $content->{'repeat-details-monthly-week-month'}
+                    || 1,
+                byday => $number . $day,
+            );
+        }
+    }
+    elsif ( $content->{'repeat-type'} eq 'yearly' ) {
+        if ( $content->{'repeat-details-yearly'} eq 'day' ) {
+            $set = DateTime::Event::ICal->recur(
+                dtstart  => $last_due || $last_created,
+                freq    => 'yearly',
+                bymonth => $content->{'repeat-details-yearly-day-month'}
+                    || 1,
+                bymonthday => $content->{'repeat-details-yearly-day-day'}
+                    || 1,
+            );
+        }
+        elsif ( $content->{'repeat-details-yearly'} eq 'week' ) {
+            my $number = $content->{'repeat-details-yearly-week-number'}
+                || 1;
+            my $day = $content->{'repeat-details-yearly-week-week'} || 'mo';
+
+            $set = DateTime::Event::ICal->recur(
+                dtstart  => $last_due || $last_created,
+                freq    => 'yearly',
+                bymonth => $content->{'repeat-details-yearly-week-month'}
+                    || 1,
+                byday => $number . $day,
+            );
+        }
+    }
+
+    return $set;
+}
+
 sub CheckCompleteStatus {
     my $ticket = shift;
     my $lifecycle =
@@ -834,10 +867,42 @@ Add this line:
 
 =back
 
+=head1 MODES
+
+=head2 Simple Mode VS Concurrent Tickets Mode
+
+This extension supports two different modes for the repeat ticket
+configurations. The extension originally only supported Concurrent
+Tickets Mode but many users found the logic counter intuitive.
+
+Any existing repeat ticket configurations from previous versions will be
+in Concurrent Tickets Mode unless the definition is changed.
+
+The default for new repeat ticket configurations is Simple Mode.
+
+=head3 Simple Mode
+
+In this mode tickets are created and start on the recurring date. If the
+lead time field is filled out the ticket will be due that many days
+after the recurring date. There is no check for existing active tickets
+and if the rt-repeat-ticket script is run multiple times for the same
+day it will create a new ticket for each run.
+
+=head3 Concurrent Tickets Mode
+
+In this mode the tickets are created with the due date as the recurring
+date. The tickets start on the due date minus the lead time. You can
+specify the max number of concurrent active tickets. If the
+rt-repeat-ticket script is run multiple times for the same day it will
+only create new tickets if there are fewer active tickets than the max
+number of concurrent active tickets.
+
 =head1 CONFIGURATION
 
 =head2 C<$RepeatTicketCoexistentNumber>
 
+Only used in Concurrent Tickets Mode.
+
 The C<$RepeatTicketCoexistentNumber>
 determines how many tickets can be in an active status for a
 recurrence at any time. A value of 1 means one ticket at a time can be active.
@@ -848,15 +913,25 @@ The extension default is 1 ticket.
 
 =head2 C<$RepeatTicketLeadTime>
 
-The C<$RepeatTicketLeadTime> becomes the ticket Starts value and sets how far
-in advance of a ticket's Due date you want the ticket to be created. This
-essentially is how long you want to give people to work on the ticket.
+When in Simple Mode the C<$RepeatTicketLeadTime> is the number of days
+to add to the recurring date for the Due date of the ticket.
+
+When in Concurrent Tickets Mode the C<$RepeatTicketLeadTime> becomes the
+ticket Starts value and sets how far in advance of a ticket's Due date
+you want the ticket to be created. This essentially is how long you want
+to give people to work on the ticket.
 
 For example, if you create a weekly recurrence scheduled on Mondays
 and set the lead time to 7 days, each Monday a ticket will be created
 with the Starts date set to that Monday and a Due date of the following
 Monday.
 
+When in Concurrent Tickets Mode, with a number of concurrent active
+tickets greater than 1, if you set the lead time to be larger than the
+interval between recurring tickets it can result in strange behavior. It
+is recommended that the ticket lead time be smaller or equal to the
+interval between tickets.
+
 The value you set in RT_SiteConfig.pm becomes the system default, but you can
 set this value on each ticket as well. The extension default is 14 days.
 
@@ -882,6 +957,16 @@ a new RT ColumnMap. You can see the available formats by looking at
 the columns available in the Display Columns portlet on the RT ticket
 search page.
 
+=head2 C<$RepeatTicketPreviewNumber>
+
+By default, the Recurrence Preview will show the next 5 tickets that will be
+created. You can modify the number of tickets to show by setting the
+C<$RepeatTicketPreviewNumber> option:
+
+    Set($RepeatTicketPreviewNumber, 10);
+
+Set the C<$RepeatTicketPreviewNumber> option to 0 to hide the Recurrence Preview.
+
 =head2 rt-repeat-ticket
 
 The rt-repeat-ticket utility evaluates all of your repeating tickets and creates
@@ -896,6 +981,11 @@ sure no repeating tickets have been missed. Just go back and run the script for
 the days you missed. You can also pass dates in the future which might be handy if
 you want to experiment with recurrences in a test environment.
 
+=head3 WARNING
+
+If you run the script multiple times for the same day then it is possible multiple
+tickets will be created for the same repeat ticket configuration.
+
 =head1 USAGE
 
 =head2 Initial Tickets
@@ -957,13 +1047,15 @@ Do you have rt-repeat-tickets scheduled in cron? Is it running?
 
 =item *
 
-Do you have previous tickets still in an active state? Resolve those tickets
-or increase the concurrent active tickets value.
+If the repeat configuration is in Concurrent Tickets Mode do you have
+previous tickets still in an active state? Resolve those tickets or
+increase the concurrent active tickets value.
 
 =item *
 
-Is it the right day? Remember to subtract the lead time value to determine
-the day new tickets should be created.
+Is it the right day? If the repeat configuration is in Concurrent
+Tickets Mode remember to subtract the lead time value to determine the
+day new tickets should be created.
 
 =item *
 
@@ -986,6 +1078,16 @@ granted for "Original Ticket" custom field.
 
 =back
 
+=head1 SEARCHING
+
+To search for tickets that have recurrence enabled use the following in a Ticket
+Search:
+
+    HasAttribute = 'RepeatTicketSettings'
+
+This will need to be added on the Advanced tab so build the rest of your search
+as desired and then add the clause on the Advanced tab.
+
 =head1 METHODS
 
 =head2 Run( RT::Attribute $attr, DateTime $checkday )

-----------------------------------------------------------------------

Summary of changes:
 Changes                             |   5 +
 MANIFEST                            |   1 +
 META.yml                            |   6 +-
 README                              |  79 +++++++-
 html/Ticket/Elements/EditRecurrence | 162 +++++++++++++++-
 inc/Module/AutoInstall.pm           |   2 +-
 inc/Module/Install.pm               |   2 +-
 inc/Module/Install/AutoInstall.pm   |   2 +-
 inc/Module/Install/Base.pm          |   2 +-
 inc/Module/Install/Can.pm           |   2 +-
 inc/Module/Install/Fetch.pm         |   2 +-
 inc/Module/Install/Include.pm       |   2 +-
 inc/Module/Install/Makefile.pm      |   2 +-
 inc/Module/Install/Metadata.pm      |  12 +-
 inc/Module/Install/RTx.pm           |   4 +-
 inc/Module/Install/Win32.pm         |   2 +-
 inc/Module/Install/WriteAll.pm      |   2 +-
 inc/YAML/Tiny.pm                    |   4 +-
 lib/RT/Extension/RepeatTicket.pm    | 362 ++++++++++++++++++++++++------------
 xt/cf.t                             |   1 +
 xt/create_on_recurring_date.t       | 109 +++++++++++
 xt/daily.t                          |  14 +-
 xt/end_conditions.t                 |  14 +-
 xt/monthly.t                        |   1 +
 xt/on_complete.t                    |   2 +
 xt/start_date.t                     |  21 ++-
 xt/weekly.t                         |  33 +++-
 xt/yearly.t                         |   1 +
 28 files changed, 660 insertions(+), 191 deletions(-)
 create mode 100644 xt/create_on_recurring_date.t


hooks/post-receive
-- 
rt-extension-repeatticket


More information about the Bps-public-commit mailing list