Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision 13fbd336

Von Moritz Bunkus vor mehr als 10 Jahren hinzugefügt

  • ID 13fbd33663c229ec309a858ac1d393fdd508ddbe
  • Vorgänger 70ba777d
  • Nachfolger 8388c9bf

Pflichtenheftversionen: Datenbankstruktur zu Pflichtenheften geändert

requirement_specs.version_id wurde durch
requirement_spec_versions.requirement_spec_id und
requirement_spec_versions.working_copy_id ersetzt.

Unterschiede anzeigen:

SL/Controller/RequirementSpec.pm
192 192

  
193 193
  my $versioned_copy = SL::DB::RequirementSpec->new(id => $::form->{versioned_copy_id})->load;
194 194

  
195
  $self->requirement_spec->copy_from(
196
    $versioned_copy,
197
    version_id => $versioned_copy->version_id,
198
  );
195
  $self->requirement_spec->copy_from($versioned_copy);
196
  my $version = $versioned_copy->versions->[0];
197
  $version->update_attributes(working_copy_id => $self->requirement_spec->id);
199 198

  
200 199
  flash_later('info', t8('The requirement spec has been reverted to version #1.', $self->requirement_spec->version->version_number));
201 200
  $self->js->redirect_to($self->url_for(action => 'show', id => $self->requirement_spec->id))->render($self);
......
410 409
                         sub      => sub { $_[0]->project_id ? $_[0]->project->projectnumber : '' } },
411 410
      status        => { sub      => sub { $_[0]->status->description } },
412 411
      type          => { sub      => sub { $_[0]->type->description } },
413
      version       => { sub      => sub { $_[0]->version_id ? $_[0]->version->version_number : t8('Working copy without version') } },
412
      version       => { sub      => sub { $_[0]->version ? $_[0]->version->version_number : t8('Working copy without version') } },
414 413
    );
415 414
  }
416 415

  
SL/Controller/RequirementSpecVersion.pm
17 17

  
18 18
use Rose::Object::MakeMethods::Generic
19 19
(
20
  'scalar --get_set_init' => [ qw(requirement_spec version js versioned_copies) ],
20
  'scalar --get_set_init' => [ qw(requirement_spec version js) ],
21 21
);
22 22

  
23 23
__PACKAGE__->run_before('check_auth');
......
117 117
  $self->js(SL::ClientJS->new);
118 118
}
119 119

  
120
sub init_versioned_copies {
121
  my ($self) = @_;
122
  $self->versioned_copies([
123
    sort { $b->mtime <=> $a->mtime } @{ $self->requirement_spec->versioned_copies }
124
  ]);
125
}
126

  
127 120
sub has_item_changed {
128 121
  my ($previous, $current) = @_;
129 122
  croak "Missing previous/current" if !$previous || !$current;
SL/DB/MetaSetup/RequirementSpec.pm
22 22
  time_estimation         => { type => 'numeric', default => '0', not_null => 1, precision => 2, scale => 12 },
23 23
  title                   => { type => 'text', not_null => 1 },
24 24
  type_id                 => { type => 'integer' },
25
  version_id              => { type => 'integer' },
26 25
  working_copy_id         => { type => 'integer' },
27 26
);
28 27

  
......
51 50
    key_columns => { type_id => 'id' },
52 51
  },
53 52

  
54
  version => {
55
    class       => 'SL::DB::RequirementSpecVersion',
56
    key_columns => { version_id => 'id' },
57
  },
58

  
59 53
  working_copy => {
60 54
    class       => 'SL::DB::RequirementSpec',
61 55
    key_columns => { working_copy_id => 'id' },
SL/DB/MetaSetup/RequirementSpecVersion.pm
9 9
__PACKAGE__->meta->table('requirement_spec_versions');
10 10

  
11 11
__PACKAGE__->meta->columns(
12
  comment        => { type => 'text' },
13
  description    => { type => 'text', not_null => 1 },
14
  id             => { type => 'serial', not_null => 1 },
15
  itime          => { type => 'timestamp', default => 'now()' },
16
  mtime          => { type => 'timestamp' },
17
  version_number => { type => 'integer' },
12
  comment             => { type => 'text' },
13
  description         => { type => 'text', not_null => 1 },
14
  id                  => { type => 'serial', not_null => 1 },
15
  itime               => { type => 'timestamp', default => 'now()' },
16
  mtime               => { type => 'timestamp' },
17
  requirement_spec_id => { type => 'integer', not_null => 1 },
18
  version_number      => { type => 'integer' },
19
  working_copy_id     => { type => 'integer' },
18 20
);
19 21

  
20 22
__PACKAGE__->meta->primary_key_columns([ 'id' ]);
21 23

  
22 24
__PACKAGE__->meta->allow_inline_column_values(1);
23 25

  
26
__PACKAGE__->meta->foreign_keys(
27
  requirement_spec => {
28
    class       => 'SL::DB::RequirementSpec',
29
    key_columns => { requirement_spec_id => 'id' },
30
  },
31

  
32
  working_copy => {
33
    class       => 'SL::DB::RequirementSpec',
34
    key_columns => { working_copy_id => 'id' },
35
  },
36
);
37

  
24 38
1;
25 39
;
SL/DB/RequirementSpec.pm
27 27
    class          => 'SL::DB::RequirementSpec',
28 28
    column_map     => { id => 'working_copy_id' },
29 29
  },
30
  versions         => {
31
    type           => 'one to many',
32
    class          => 'SL::DB::RequirementSpecVersion',
33
    column_map     => { id => 'requirement_spec_id' },
34
  },
35
  working_copy_versions => {
36
    type           => 'one to many',
37
    class          => 'SL::DB::RequirementSpecVersion',
38
    column_map     => { id => 'working_copy_id' },
39
  },
30 40
  orders           => {
31 41
    type           => 'one to many',
32 42
    class          => 'SL::DB::RequirementSpecOrder',
......
56 66
  return 1;
57 67
}
58 68

  
69
sub version {
70
  my ($self) = @_;
71

  
72
  croak "Not a writer" if scalar(@_) > 1;
73

  
74
  return $self->is_working_copy ? $self->working_copy_versions->[0] : $self->versions->[0];
75
}
76

  
59 77
sub text_blocks_sorted {
60 78
  my ($self, %params) = _hashify(1, @_);
61 79

  
......
222 240
sub next_version_number {
223 241
  my ($self) = @_;
224 242

  
225
  return max(0, map { $_->version->version_number } @{ $self->versioned_copies }) + 1;
243
  return 1 if !$self->id;
244

  
245
  my ($max_number) = $self->db->dbh->selectrow_array(<<SQL, {}, $self->id, $self->id);
246
    SELECT MAX(v.version_number)
247
    FROM requirement_spec_versions v
248
    WHERE v.requirement_spec_id IN (
249
      SELECT rs.id
250
      FROM requirement_specs rs
251
      WHERE (rs.id              = ?)
252
         OR (rs.working_copy_id = ?)
253
    )
254
SQL
255

  
256
  return ($max_number // 0) + 1;
226 257
}
227 258

  
228 259
sub create_version {
......
234 265
  my $ok = $self->db->with_transaction(sub {
235 266
    delete $attributes{version_number};
236 267

  
237
    $version = SL::DB::RequirementSpecVersion->new(%attributes, version_number => $self->next_version_number)->save;
238
    $copy    = $self->create_copy;
239
    $copy->update_attributes(version_id => $version->id, working_copy_id => $self->id);
240
    $self->update_attributes(version_id => $version->id);
268
    SL::DB::Manager::RequirementSpecVersion->update_all(
269
      set   => [ working_copy_id     => undef     ],
270
      where => [ requirement_spec_id => $self->id ],
271
    );
272

  
273
    $copy    = $self->create_copy(working_copy_id => $self->id);
274
    $version = SL::DB::RequirementSpecVersion->new(%attributes, version_number => $self->next_version_number, requirement_spec_id => $copy->id, working_copy_id => $self->id)->save;
241 275

  
242 276
    1;
243 277
  });
......
250 284

  
251 285
  croak "Cannot work on a versioned copy" if $self->working_copy_id;
252 286

  
253
  return if !$self->id || !$self->version_id;
254
  $self->update_attributes(version_id => undef);
287
  return if !$self->id;
288

  
289
  SL::DB::Manager::RequirementSpecVersion->update_all(
290
    set   => [ working_copy_id => undef     ],
291
    where => [ working_copy_id => $self->id ],
292
  );
255 293
}
256 294

  
257 295
1;
......
271 309
important thing is how working copy/versions are handled.
272 310

  
273 311
The table contains three important columns: C<id> (which is also the
274
primary key), C<working_copy_id> and C<version_id>. C<working_copy_id>
275
is a self-referencing column: it can be C<NULL>, but if it isn't then
276
it contains another requirement spec C<id>. C<version_id> on the other
277
hand references the table C<requirement_spec_versions>.
312
primary key) and C<working_copy_id>. C<working_copy_id> is a
313
self-referencing column: it can be C<NULL>, but if it isn't then it
314
contains another requirement spec C<id>.
315

  
316
Versions are represented similarly. The C<requirement_spec_versions>
317
table has three important columns: C<id> (the primary key),
318
C<requirement_spec_id> (references C<requirement_specs.id> and must
319
not be C<NULL>) and C<working_copy_id> (references
320
C<requirement_specs.id> as well but can be
321
C<NULL>). C<working_copy_id> points to the working copy if and only if
322
the working copy is currently equal to a versioned copy.
278 323

  
279 324
The design is as follows:
280 325

  
......
289 334
in time it was created. Each versioned copy refers back to the working
290 335
copy it belongs to: each has its C<working_copy_id> set.
291 336

  
292
=item * Each versioned copy must reference an entry in the table
293
C<requirement_spec_versions>. Meaning: for each versioned copy
294
C<version_id> must not be C<NULL>.
337
=item * Each versioned copy must be referenced from an entry in the
338
table C<requirement_spec_versions> via
339
C<requirement_spec_id>.
295 340

  
296 341
=item * Directly after creating a versioned copy even the working copy
297
itself points to a certain version via its C<version_id> column: to
298
the same version that the versioned copy just created points
299
to. However, any modification that will be visible to the customer
300
(text, positioning etc but not internal things like time/cost
301
estimation changes) will cause the working copy to be set to 'no
302
version' again. This is achieved via before save hooks in Perl.
342
itself is referenced from a version via that table's
343
C<working_copy_id> column. However, any modification that will be
344
visible to the customer (text, positioning etc but not internal things
345
like time/cost estimation changes) will cause the version to be
346
disassociated from the working copy. This is achieved via before save
347
hooks in Perl.
303 348

  
304 349
=back
305 350

  
......
359 404
This function can be used for resetting a working copy to a specific
360 405
version. Example:
361 406

  
362
 my $requirement_spec = SL::DB::RequirementSpec->new(id => $::form->{id})->load;
363
 my $versioned_copy   = SL::DB::RequirementSpec->new(id => $::form->{versioned_copy_id})->load;
407
  my $requirement_spec = SL::DB::RequirementSpec->new(id => $::form->{id})->load;
408
  my $versioned_copy   = SL::DB::RequirementSpec->new(id => $::form->{versioned_copy_id})->load;
364 409

  
365
  $requirement_spec->copy_from(
366
    $versioned_copy,
367
    version_id => $versioned_copy->version_id,
368
  );
410
  $requirement_spec->copy_from($versioned_copy);
411
  $versioned_copy->version->update_attributes(working_copy_id => $requirement_spec->id);
369 412

  
370 413
=item C<create_copy>
371 414

  
......
385 428
=item 1. The next version number is calculated using
386 429
L</next_version_number>.
387 430

  
388
=item 2. An instance of L<SL::DB::RequirementSpecVersion> is
431
=item 2. A copy of C<$self> is created with L</create_copy>.
432

  
433
=item 3. An instance of L<SL::DB::RequirementSpecVersion> is
389 434
created. Its attributes are copied from C<%attributes> save for the
390 435
version number which is taken from step 1.
391 436

  
392
=item 3. A copy of C<$self> is created with L</create_copy>.
393

  
394
=item 4. The version instance created in step is assigned to the copy
395
from step 3.
396

  
397
=item 5. The C<version_id> in C<$self> is set to the copy's ID from
398
step 3.
437
=item 4. The version instance created in step 3 is referenced to the
438
the copy from step 2 via C<requirement_spec_id> and to the working
439
copy for which the version was created via C<working_copy_id>.
399 440

  
400 441
=back
401 442

  
......
424 465
Prerequisites: C<$self> must be a working copy (see the overview),
425 466
not a versioned copy.
426 467

  
427
Sets the C<version_id> field to C<undef> and saves C<$self>.
468
Sets any C<working_copy_id> field in the C<requirement_spec_versions>
469
table containing C<$self-E<gt>id> to C<undef>.
428 470

  
429 471
=item C<is_working_copy>
430 472

  
SL/DB/RequirementSpecItem.pm
66 66
sub _before_save_invalidate_requirement_spec_version {
67 67
  my ($self, %params) = @_;
68 68

  
69
  return 1 if !$self->requirement_spec_id;
69
  return 1 if !$self->requirement_spec_id || $self->requirement_spec->working_copy_id;
70 70

  
71 71
  my %changed_columns = map { $_ => 1 } (Rose::DB::Object::Helpers::dirty_columns($self));
72 72
  my $has_changed     = !Rose::DB::Object::Util::is_in_db($self);
SL/DB/RequirementSpecTextBlock.pm
30 30
sub _before_save_invalidate_requirement_spec_version {
31 31
  my ($self, %params) = @_;
32 32

  
33

  
34
  return 1 if !$self->requirement_spec_id;
33
  return 1 if !$self->requirement_spec_id || $self->requirement_spec->working_copy_id;
35 34

  
36 35
  my %changed_columns = map { $_ => 1 } (Rose::DB::Object::Helpers::dirty_columns($self));
37 36

  
sql/Pg-upgrade2/requirement_spec_delete_trigger_fix2.sql
1
-- @tag: requirement_spec_delete_trigger_fix2
2
-- @description: Fixes für Delete-Trigger bei Pflichtenheften
3
-- @depends: requirement_spec_delete_trigger_fix
4

  
5
-- requirement_spec_id: link to requirement specs (the versioned
6
-- document) working_copy_id: link to requirement spec working copy
7
-- (only set if working copy is currently at a version level)
8
ALTER TABLE requirement_spec_versions ADD COLUMN requirement_spec_id INTEGER;
9
ALTER TABLE requirement_spec_versions ADD COLUMN working_copy_id     INTEGER;
10

  
11
UPDATE requirement_spec_versions ver
12
SET requirement_spec_id = (
13
  SELECT MAX(rs.id)
14
  FROM requirement_specs rs
15
  WHERE rs.version_id = ver.id
16
);
17

  
18
UPDATE requirement_spec_versions ver
19
SET working_copy_id = (
20
  SELECT rs.id
21
  FROM requirement_specs rs
22
  WHERE (rs.version_id = ver.id)
23
    AND (rs.working_copy_id IS NULL)
24
);
25

  
26
ALTER TABLE requirement_spec_versions ALTER COLUMN requirement_spec_id SET NOT NULL;
27
ALTER TABLE requirement_spec_versions ADD FOREIGN KEY (requirement_spec_id) REFERENCES requirement_specs (id) ON DELETE CASCADE;
28
ALTER TABLE requirement_spec_versions ADD FOREIGN KEY (working_copy_id)     REFERENCES requirement_specs (id) ON DELETE CASCADE;
29

  
30
ALTER TABLE requirement_specs DROP COLUMN version_id;
31
ALTER TABLE requirement_specs DROP CONSTRAINT requirement_specs_working_copy_id_fkey;
32
ALTER TABLE requirement_specs ADD FOREIGN KEY (working_copy_id) REFERENCES requirement_specs (id) ON DELETE CASCADE;
33

  
34
ALTER TABLE requirement_spec_items DROP CONSTRAINT requirement_spec_items_requirement_spec_id_fkey;
35
ALTER TABLE requirement_spec_items ADD FOREIGN KEY (requirement_spec_id) REFERENCES requirement_specs (id) ON DELETE CASCADE;
36

  
37
ALTER TABLE requirement_spec_item_dependencies DROP CONSTRAINT requirement_spec_item_dependencies_depended_item_id_fkey;
38
ALTER TABLE requirement_spec_item_dependencies ADD FOREIGN KEY (depended_item_id) REFERENCES requirement_spec_items (id) ON DELETE CASCADE;
39
ALTER TABLE requirement_spec_item_dependencies DROP CONSTRAINT requirement_spec_item_dependencies_depending_item_id_fkey;
40
ALTER TABLE requirement_spec_item_dependencies ADD FOREIGN KEY (depending_item_id) REFERENCES requirement_spec_items (id) ON DELETE CASCADE;
41

  
42
ALTER TABLE requirement_spec_text_blocks DROP CONSTRAINT requirement_spec_text_blocks_requirement_spec_id_fkey;
43
ALTER TABLE requirement_spec_text_blocks ADD FOREIGN KEY (requirement_spec_id) REFERENCES requirement_specs (id) ON DELETE CASCADE;
44

  
45
-- Trigger for deleting depending stuff if a requirement spec is deleted.
46
CREATE OR REPLACE FUNCTION requirement_spec_delete_trigger() RETURNS trigger AS $$
47
  BEGIN
48
    IF TG_WHEN = 'AFTER' THEN
49
      DELETE FROM trigger_information WHERE (key = 'deleting_requirement_spec') AND (value = CAST(OLD.id AS TEXT));
50

  
51
      RETURN OLD;
52
    END IF;
53

  
54
    RAISE DEBUG 'before delete trigger on %', OLD.id;
55

  
56
    INSERT INTO trigger_information (key, value) VALUES ('deleting_requirement_spec', CAST(OLD.id AS TEXT));
57

  
58
    RAISE DEBUG '  Converting items into sections items for %', OLD.id;
59
    UPDATE requirement_spec_items SET item_type  = 'section', parent_id = NULL WHERE requirement_spec_id = OLD.id;
60

  
61
    RAISE DEBUG '  And we out for %', OLD.id;
62

  
63
    RETURN OLD;
64
  END;
65
$$ LANGUAGE plpgsql;
66

  
67
-- Trigger for deleting depending stuff if a requirement spec item is deleted.
68
CREATE OR REPLACE FUNCTION requirement_spec_item_before_delete_trigger() RETURNS trigger AS $$
69
  BEGIN
70
    RAISE DEBUG 'delete trig RSitem old id %', OLD.id;
71
    INSERT INTO trigger_information (key, value) VALUES ('deleting_requirement_spec_item', CAST(OLD.id AS TEXT));
72
    DELETE FROM requirement_spec_items WHERE (parent_id         = OLD.id);
73
    DELETE FROM trigger_information    WHERE (key = 'deleting_requirement_spec_item') AND (value = CAST(OLD.id AS TEXT));
74
    RAISE DEBUG 'delete trig END %', OLD.id;
75
    RETURN OLD;
76
  END;
77
$$ LANGUAGE plpgsql;
templates/print/Standard/requirement_spec.tex
4 4
$( USE P )$
5 5
\documentclass{scrartcl}
6 6

  
7
\usepackage[reqspeclogo,$( IF !rspec.version_id )$draftlogo$( ELSE )$secondpagelogo$( END )$]{kivitendo}
7
\usepackage[reqspeclogo,$( IF !rspec.version )$draftlogo$( ELSE )$secondpagelogo$( END )$]{kivitendo}
8 8

  
9 9
\kivitendobgsettings
10 10

  
......
15 15
  \parbox{12cm}{%
16 16
    \defaultfont\scriptsize%
17 17
    $( LxLatex.filter(rspec.displayable_name) )$\\
18
    $( !rspec.version_id ? "Arbeitskopie ohne Version" : "Version " _ rspec.version.version_number _ " vom " _ rspec.version.itime.to_kivitendo(precision='minute') )$
18
    $( !rspec.version ? "Arbeitskopie ohne Version" : "Version " _ rspec.version.version_number _ " vom " _ rspec.version.itime.to_kivitendo(precision='minute') )$
19 19

  
20 20
    \vspace*{0.2cm}%
21 21
    Seite \thepage%
......
44 44
      \Large
45 45
      $( LxLatex.filter(rspec.title) )$
46 46
      \normalsize
47
%$( IF rspec.version_id )$
47
%$( IF rspec.version )$
48 48

  
49 49
    Version $( LxLatex.filter(rspec.version.version_number) )$
50 50
%$( END )$
......
66 66
\vspace*{0.7cm}
67 67

  
68 68
%$( SET working_copy     = rspec.working_copy_id ? rspec.working_copy : rspec )$
69
%$( SET versioned_copies = rspec.version_id ? working_copy.versioned_copies_sorted(max_version_number = rspec.version.version_number) : working_copy.versioned_copies_sorted )$
69
%$( SET versioned_copies = rspec.version ? working_copy.versioned_copies_sorted(max_version_number = rspec.version.version_number) : working_copy.versioned_copies_sorted )$
70 70
%$( IF !versioned_copies.size )$
71 71
  Bisher wurden noch keine Versionen angelegt.
72 72
%$( ELSE )$
templates/webpages/requirement_spec/_version.html
1 1
[%- USE L -%][%- USE LxERP -%][%- USE HTML -%]
2
[% L.hidden_tag('current_version_id', requirement_spec.version_id) %]
2
[% L.hidden_tag('current_version_id', requirement_spec.version.id) %]
3 3
[% LxERP.t8("Current version") %]:
4
[% IF !requirement_spec.version_id %]
4
[% IF !requirement_spec.version.id %]
5 5
 [% LxERP.t8("Working copy without version") %]
6 6
[% ELSE %]
7 7
 [% LxERP.t8("Version") %] [% HTML.escape(requirement_spec.version.version_number) %]
templates/webpages/requirement_spec_version/list.html
12 12

  
13 13
 <tbody>
14 14
  <tr class="listrow versioned-copy-context-menu">
15
   [%- IF SELF.requirement_spec.version_id %]
16
    [% L.hidden_tag('versioned_copy_id', SELF.requirement_spec.version_id, no_id=1) %]
15
   [%- IF SELF.requirement_spec.version %]
16
    [% L.hidden_tag('versioned_copy_id', SELF.requirement_spec.version.requirement_spec_id, no_id=1) %]
17 17
    <td>[%- LxERP.t8("Working copy identical to version number #1", SELF.requirement_spec.version.version_number) %]</td>
18 18
   [%- ELSE %]
19 19
    <td>[%- LxERP.t8("Working copy without version") %]</td>
......
23 23
   <td>[% SELF.requirement_spec.mtime.to_kivitendo(precision='minute') %]</td>
24 24
  </tr>
25 25

  
26
  [%- FOREACH versioned = SELF.versioned_copies %]
26
  [%- FOREACH versioned_copy = SELF.requirement_spec.versioned_copies_sorted %]
27
   [%- SET version = versioned_copy.version %]
27 28
   <tr class="listrow versioned-copy-context-menu">
28
    [% L.hidden_tag('versioned_copy_id', versioned.id, no_id=1) %]
29
    <td>[% HTML.escape(versioned.version.version_number) %]</td>
30
    <td>[% HTML.escape(P.truncate(versioned.description)) %]</td>
31
    <td>[% HTML.escape(P.truncate(versioned.comment)) %]</td>
32
    <td>[% versioned.mtime.to_kivitendo(precision='minute') %]</td>
29
    [% L.hidden_tag('versioned_copy_id', versioned_copy.id, no_id=1) %]
30
    <td>[% HTML.escape(version.version_number) %]</td>
31
    <td>[% HTML.escape(P.truncate(version.description)) %]</td>
32
    <td>[% HTML.escape(P.truncate(version.comment)) %]</td>
33
    <td>[% version.itime.to_kivitendo(precision='minute') %]</td>
33 34
   </tr>
34 35
  [%- END %]
35 36
 </tbody>

Auch abrufbar als: Unified diff