From: Moritz Bunkus Date: Fri, 3 May 2013 09:22:49 +0000 (+0200) Subject: Pflichtenhefte: Dokumentation; Refactoring; Bugfix Diff-Berechnung X-Git-Tag: release-3.2.0beta~467^2~174 X-Git-Url: http://wagnertech.de/git?a=commitdiff_plain;h=013804fdf4b69b04889d8bf8af182eda9e9d95a5;p=kivitendo-erp.git Pflichtenhefte: Dokumentation; Refactoring; Bugfix Diff-Berechnung --- diff --git a/SL/Controller/RequirementSpecVersion.pm b/SL/Controller/RequirementSpecVersion.pm index 058884709..97b5bf319 100644 --- a/SL/Controller/RequirementSpecVersion.pm +++ b/SL/Controller/RequirementSpecVersion.pm @@ -37,13 +37,15 @@ sub action_new { $self->version(SL::DB::RequirementSpecVersion->new); - my $previous_version = $self->requirement_spec->previous_version; - my %differences = $self->calculate_differences(current => $self->requirement_spec, previous => $previous_version); + my $previous_version = $self->requirement_spec->highest_version; + $::lxdebug->message(0, "vers num " . $previous_version->version->version_number . " id " . $previous_version->version_id); if (!$previous_version) { $self->version->description(t8('Initial version.')); } else { + my %differences = $self->calculate_differences(current => $self->requirement_spec, previous => $previous_version); + my @lines; my $fb_diff = $differences{function_blocks}; @@ -124,7 +126,10 @@ sub init_versioned_copies { sub has_item_changed { my ($previous, $current) = @_; croak "Missing previous/current" if !$previous || !$current; - return any { ($previous->$_ || '') ne ($current->$_ || '') } qw(item_type parent_id fb_number title description complexity_id risk_id time_estimation net_sum); + + return 1 if any { ($previous->$_ || '') ne ($current->$_ || '') } qw(item_type fb_number title description complexity_id risk_id); + return 0 if !$current->parent_id; + return $previous->parent->fb_number ne $current->parent->fb_number; } sub has_text_block_changed { diff --git a/SL/DB/RequirementSpec.pm b/SL/DB/RequirementSpec.pm index 12afb2483..d8329b93a 100644 --- a/SL/DB/RequirementSpec.pm +++ b/SL/DB/RequirementSpec.pm @@ -3,6 +3,7 @@ package SL::DB::RequirementSpec; use strict; use Carp; +use List::Util qw(max reduce); use Rose::DB::Object::Helpers; use SL::DB::MetaSetup::RequirementSpec; @@ -110,7 +111,7 @@ sub _create_copy { return $copy; } -sub copy_from { +sub _copy_from { my ($self, $source, %attributes) = @_; croak "Missing parameter 'source'" unless $source; @@ -156,17 +157,16 @@ sub copy_from { return $self; } -sub previous_version { - my ($self) = @_; +sub copy_from { + my ($self, $source, %attributes) = @_; - my $and = $self->version_id ? " AND (version_id <> ?)" : ""; - my $id = $self->db->dbh->selectrow_array(<id, ($self->version_id) x !!$self->version_id); - SELECT MAX(id) - FROM requirement_specs - WHERE (working_copy_id = ?) $and -SQL + $self->db->with_transaction(sub { $self->_copy_from($source, %attributes); }); +} - return $id ? SL::DB::RequirementSpec->new(id => $id)->load : undef; +sub highest_version { + my ($self) = @_; + + return reduce { $a->version->version_number > $b->version->version_number ? $a : $b } @{ $self->versioned_copies }; } sub is_working_copy { @@ -177,14 +177,8 @@ sub is_working_copy { sub next_version_number { my ($self) = @_; - my $max_number = $self->db->dbh->selectrow_array(<id); - SELECT COALESCE(MAX(ver.version_number), 0) - FROM requirement_spec_versions ver - JOIN requirement_specs rs ON (rs.version_id = ver.id) - WHERE rs.working_copy_id = ? -SQL - - return $max_number + 1; + + return max(0, map { $_->version->version_number } @{ $self->versioned_copies }) + 1; } sub create_version { @@ -193,7 +187,7 @@ sub create_version { croak "Cannot work on a versioned copy" if $self->working_copy_id; my ($copy, $version); - my $ok = $self->db->do_transaction(sub { + my $ok = $self->db->with_transaction(sub { delete $attributes{version_number}; $version = SL::DB::RequirementSpecVersion->new(%attributes, version_number => $self->next_version_number)->save; @@ -217,3 +211,232 @@ sub invalidate_version { } 1; +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +SL::DB::RequirementSpec - RDBO model for requirement specs + +=head1 OVERVIEW + +The database structure behind requirement specs is a bit involved. The +important thing is how working copy/versions are handled. + +The table contains three important columns: C (which is also the +primary key), C and C. C +is a self-referencing column: it can be C, but if it isn't then +it contains another requirement spec C. C on the other +hand references the table C. + +The design is as follows: + +=over 2 + +=item * The user is always working on a working copy. The working copy +is identified in the database by having C set to +C. + +=item * All other entries in this table are referred to as I. A versioned copy is a copy of a working frozen at the moment +in time it was created. Each versioned copy refers back to the working +copy it belongs to: each has its C set. + +=item * Each versioned copy must reference an entry in the table +C. Meaning: for each versioned copy +C must not be C. + +=item * Directly after creating a versioned copy even the working copy +itself points to a certain version via its C column: to +the same version that the versioned copy just created points +to. However, any modification that will be visible to the customer +(text, positioning etc but not internal things like time/cost +estimation changes) will cause the working copy to be set to 'no +version' again. This is achieved via before save hooks in Perl. + +=back + +=head1 DATABASE TRIGGERS AND CHECKS + +Several database triggers and consistency checks exist that manage +requirement specs, their items and their dependencies. These are +described here instead of in the individual files for the other RDBO +models. + +=head2 DELETION + +When you delete a requirement spec all of its dependencies (items, +text blocks, versions etc.) are deleted by triggers. + +When you delete an item (either a section or a (sub-)function block) +all of its children will be deleted as well. This will trigger the +same trigger resulting in a recursive deletion with the bottom-most +items being deleted first. Their item dependencies are deleted as +well. + +=head2 UPDATING + +Whenever you update a requirement spec item a trigger will fire that +will update the parent's C column. This also happens +when an item is deleted or updated. + +=head2 CONSISTENCY CHECKS + +Several consistency checks are applied to requirement spec items: + +=over 2 + +=item * Column C can only contain one of +the values C
, C or C. + +=item * Column C must be C if +C is set to C
and C otherwise. + +=back + +=head1 FUNCTIONS + +=over 4 + +=item C + +Copies everything (basic attributes like type/title/customer, items, +text blocks, time/cost estimation) save for the versions from the +other requirement spec object C<$source> into C<$self> and saves +it. This is done within a transaction. + +C<%attributes> are attributes that are assigned to C<$self> after all +the basic attributes from C<$source> have been assigned. + +This function can be used for resetting a working copy to a specific +version. Example: + + my $requirement_spec = SL::DB::RequirementSpec->new(id => $::form->{id})->load; + my $versioned_copy = SL::DB::RequirementSpec->new(id => $::form->{versioned_copy_id})->load; + + $requirement_spec->copy_from( + $versioned_copy, + version_id => $versioned_copy->version_id, + ); + +=item C + +Creates and returns a copy of C<$self>. The copy is already +saved. Creating the copy happens within a transaction. + +=item C + +Prerequisites: C<$self> must be a working copy (see L), +not a versioned copy. + +This function creates a new version for C<$self>. This involves +several steps: + +=over 2 + +=item 1. The next version number is calculated using +L. + +=item 2. An instance of L is +created. Its attributes are copied from C<%attributes> save for the +version number which is taken from step 1. + +=item 3. A copy of C<$self> is created with L. + +=item 4. The version instance created in step is assigned to the copy +from step 3. + +=item 5. The C in C<$self> is set to the copy's ID from +step 3. + +=back + +All this is done within a transaction. + +In case of success a two-element list is returned consisting of the +copy & version objects created in steps 3 and 2 respectively. In case +of a failure an empty list will be returned. + +=item C + +Returns a human-readable name for this instance consisting of the type +and the title. + +=item C + +Given a working copy C<$self> this function returns the versioned copy +of C<$self> with the highest version number. If such a version exist +its instance is returned. Otherwise C is returned. + +This can be used for calculating the difference between the working +copy and the last version created for it. + +=item C + +Prerequisites: C<$self> must be a working copy (see L), +not a versioned copy. + +Sets the C field to C and saves C<$self>. + +=item C + +Returns trueish if C<$self> is a working copy and not a versioned +copy. The condition for this is that C is C. + +=item C + +Calculates and returns the next version number for this requirement +spec. Version numbers start at 1 and are incremented by one for each +version created for it, no matter whether or not it has been reverted +to a previous version since. It boils down to this pseudo-code: + + if (has_never_had_a_version) + return 1 + else + return max(version_number for all versions for this requirement spec) + 1 + +=item C + +An alias for L. + +=item C + +Returns a list of requirement spec items that + +This is not a writer. Use the C relationship for that. + +=item C + +Returns an array (or an array reference depending on context) of text +blocks sorted by their positional column in ascending order. If the +C parameter is given then only the text blocks +belonging to that C are returned. + +=item C + +Validate values before saving. Returns list or human-readable error +messages (if any). + +=item C + +Returns an array (or an array reference depending on context) of +versioned copies sorted by their version number in ascending order. If +the C parameter is given then only the versioned +copies whose version number is less than or equal to +C are returned. + +=back + +=head1 BUGS + +Nothing here yet. + +=head1 AUTHOR + +Moritz Bunkus Em.bunkus@linet-services.deE + +=cut diff --git a/SL/DB/RequirementSpecItem.pm b/SL/DB/RequirementSpecItem.pm index 641982cf1..5e7d46c91 100644 --- a/SL/DB/RequirementSpecItem.pm +++ b/SL/DB/RequirementSpecItem.pm @@ -126,3 +126,52 @@ sub child_type { } 1; +__END__ + +=pod + +=encoding utf8 + +=head1 NAME + +SL::DB::RequirementSpecItem - Items for requirement specs + +=head1 OVERVIEW + +Please see L for the architectual overview. + +=head1 FUNCTIONS + +=over 4 + +=item C + +Returns the C for children of C<$self>. + +=item C + +Returns an array (or an array reference depending on context) of +direct children (not of grandchildren) for C<$self> ordered by their +positional column in ascending order. + +=item C
+ +Returns the section this item belongs to. It can be C<$self> if +C<$self> is already a section, its parent or grandparent. + +=item C + +Validates before saving and returns an array of human-readable error +messages in case of an error. + +=back + +=head1 BUGS + +Nothing here yet. + +=head1 AUTHOR + +Moritz Bunkus Em.bunkus@linet-services.deE + +=cut