Pflichtenhefte auf Versionen zurücksetzen können
[kivitendo-erp.git] / SL / DB / RequirementSpec.pm
1 package SL::DB::RequirementSpec;
2
3 use strict;
4
5 use Carp;
6 use Rose::DB::Object::Helpers;
7
8 use SL::DB::MetaSetup::RequirementSpec;
9 use SL::DB::Manager::RequirementSpec;
10 use SL::Locale::String;
11
12 __PACKAGE__->meta->add_relationship(
13   items            => {
14     type           => 'one to many',
15     class          => 'SL::DB::RequirementSpecItem',
16     column_map     => { id => 'requirement_spec_id' },
17   },
18   text_blocks      => {
19     type           => 'one to many',
20     class          => 'SL::DB::RequirementSpecTextBlock',
21     column_map     => { id => 'requirement_spec_id' },
22   },
23   versioned_copies => {
24     type           => 'one to many',
25     class          => 'SL::DB::RequirementSpec',
26     column_map     => { id => 'working_copy_id' },
27   },
28 );
29
30 __PACKAGE__->meta->initialize;
31
32 __PACKAGE__->before_save('_before_save_initialize_not_null_columns');
33
34 sub validate {
35   my ($self) = @_;
36
37   my @errors;
38   push @errors, t8('The title is missing.') if !$self->title;
39
40   return @errors;
41 }
42
43 sub _before_save_initialize_not_null_columns {
44   my ($self) = @_;
45
46   $self->previous_section_number(0) if !defined $self->previous_section_number;
47   $self->previous_fb_number(0)      if !defined $self->previous_fb_number;
48
49   return 1;
50 }
51
52 sub text_blocks_for_position {
53   my ($self, $output_position) = @_;
54
55   return [ sort { $a->position <=> $b->position } grep { $_->output_position == $output_position } @{ $self->text_blocks } ];
56 }
57
58 sub sections {
59   my ($self, @rest) = @_;
60
61   croak "This sub is not a writer" if @rest;
62
63   return [ sort { $a->position <=> $b->position } grep { !$_->parent_id } @{ $self->items } ];
64 }
65
66 sub displayable_name {
67   my ($self) = @_;
68
69   return sprintf('%s: "%s"', $self->type->description, $self->title);
70 }
71
72 sub create_copy {
73   my ($self, %params) = @_;
74
75   return $self->_create_copy(%params) if $self->db->in_transaction;
76
77   my $copy;
78   if (!$self->db->do_transaction(sub { $copy = $self->_create_copy(%params) })) {
79     $::lxdebug->message(LXDebug->WARN(), "create_copy failed: " . join("\n", (split(/\n/, $self->db->error))[0..2]));
80     return undef;
81   }
82
83   return $copy;
84 }
85
86 sub _create_copy {
87   my ($self, %params) = @_;
88
89   my $copy = Rose::DB::Object::Helpers::clone_and_reset($self);
90   $copy->copy_from($self, %params);
91
92   return $copy;
93 }
94
95 sub copy_from {
96   my ($self, $source, %attributes) = @_;
97
98   croak "Missing parameter 'source'" unless $source;
99
100   # Copy attributes.
101   $self->assign_attributes(map({ ($_ => $source->$_) } qw(type_id status_id customer_id project_id title hourly_rate net_sum previous_section_number previous_fb_number is_template)),
102                            %attributes);
103
104   # Clone text blocks.
105   $self->text_blocks(map { Rose::DB::Object::Helpers::clone_and_reset($_) } @{ $source->text_blocks });
106
107   # Save new object -- we need its ID for the items.
108   $self->save;
109
110   my %id_to_clone;
111
112   # Clone items.
113   my $clone_item;
114   $clone_item = sub {
115     my ($item) = @_;
116     my $cloned = Rose::DB::Object::Helpers::clone_and_reset($item);
117     $cloned->requirement_spec_id($self->id);
118     $cloned->children(map { $clone_item->($_) } @{ $item->children });
119
120     $id_to_clone{ $item->id } = $cloned;
121
122     return $cloned;
123   };
124
125   $self->items(map { $clone_item->($_) } @{ $source->sections });
126
127   # Save the items -- need to do that before setting dependencies.
128   $self->save;
129
130   # Set dependencies.
131   foreach my $item (@{ $source->items }) {
132     next unless @{ $item->dependencies };
133     $id_to_clone{ $item->id }->update_attributes(dependencies => [ map { $id_to_clone{$_->id} } @{ $item->dependencies } ]);
134   }
135
136   $self->update_attributes(%attributes);
137
138   return $self;
139 }
140
141 sub delete_items {
142   my ($self) = @_;
143
144   my $worker = sub {
145     # First convert all items to sections so that deleting won't
146     # violate foreign key constraints on parent_id.
147     SL::DB::Manager::RequirementSpecItem->update_all(
148       set   => { parent_id => undef, item_type => 'section' },
149       where => [
150         requirement_spec_id => $self->id,
151         '!parent_id'        => undef,
152       ]);
153
154     # Now delete all items in one go.
155     SL::DB::Manager::RequirementSpecItem->delete_all(where => [ requirement_spec_id => $self->id ]);
156
157     # Last clear values in ourself.
158     $self->items([]);
159   };
160
161   return $self->db->in_transaction ? $worker->() : $self->db->do_transaction($worker);
162 }
163
164 sub previous_version {
165   my ($self) = @_;
166
167   my $and    = $self->version_id ? " AND (version_id <> ?)" : "";
168   my $id     = $self->db->dbh->selectrow_array(<<SQL, undef, $self->id, ($self->version_id) x !!$self->version_id);
169    SELECT MAX(id)
170    FROM requirement_specs
171    WHERE (working_copy_id = ?) $and
172 SQL
173
174   return $id ? SL::DB::RequirementSpec->new(id => $id)->load : undef;
175 }
176
177 sub is_working_copy {
178   my ($self) = @_;
179
180   return !$self->working_copy_id;
181 }
182
183 sub next_version_number {
184   my ($self) = @_;
185   my $max_number = $self->db->dbh->selectrow_array(<<SQL, undef, $self->id);
186     SELECT COALESCE(MAX(ver.version_number), 0)
187     FROM requirement_spec_versions ver
188     JOIN requirement_specs rs ON (rs.version_id = ver.id)
189     WHERE rs.working_copy_id = ?
190 SQL
191
192   return $max_number + 1;
193 }
194
195 sub create_version {
196   my ($self, %attributes) = @_;
197
198   my ($copy, $version);
199   my $ok = $self->db->do_transaction(sub {
200     delete $attributes{version_number};
201
202     $version = SL::DB::RequirementSpecVersion->new(%attributes, version_number => $self->next_version_number)->save;
203     $copy    = $self->create_copy;
204     $copy->update_attributes(version_id => $version->id, working_copy_id => $self->id);
205     $self->update_attributes(version_id => $version->id);
206
207     1;
208   });
209
210   return $ok ? ($copy, $version) : ();
211 }
212
213 sub invalidate_version {
214   my ($self, %params) = @_;
215
216   $::lxdebug->message(0, "Invalidate version called for id " . $self->id . " version " . $self->version_id);
217   $::lxdebug->show_backtrace(1);
218
219   return if !$self->id || !$self->version_id;
220   $self->update_attributes(version_id => undef);
221 }
222
223 1;