Refactoring
[kivitendo-erp.git] / SL / DB / RequirementSpecItem.pm
1 package SL::DB::RequirementSpecItem;
2
3 use strict;
4
5 use Carp;
6 use List::MoreUtils qw(any);
7 use Rose::DB::Object::Helpers;
8 use Rose::DB::Object::Util;
9
10 use SL::DB::MetaSetup::RequirementSpecItem;
11 use SL::DB::Manager::RequirementSpecItem;
12 use SL::DB::Helper::ActsAsList;
13 use SL::DB::Helper::AttrDuration;
14 use SL::DB::Default;
15 use SL::Locale::String;
16 use SL::PrefixedNumber;
17
18 __PACKAGE__->meta->add_relationship(
19   children     => {
20     type       => 'one to many',
21     class      => 'SL::DB::RequirementSpecItem',
22     column_map => { id => 'parent_id' },
23   },
24   dependencies => {
25     map_class  => 'SL::DB::RequirementSpecDependency',
26     map_from   => 'depending_item',
27     map_to     => 'depended_item',
28     type       => 'many to many',
29   },
30   dependents   => {
31     map_class  => 'SL::DB::RequirementSpecDependency',
32     map_from   => 'depended_item',
33     map_to     => 'depending_item',
34     type       => 'many to many',
35   },
36 );
37
38 __PACKAGE__->meta->initialize;
39
40 __PACKAGE__->configure_acts_as_list(group_by => [qw(requirement_spec_id parent_id)]);
41 __PACKAGE__->attr_duration(qw(time_estimation));
42
43 __PACKAGE__->before_save(\&_before_save_create_fb_number);
44 __PACKAGE__->before_save(\&_before_save_invalidate_requirement_spec_version);
45 __PACKAGE__->before_delete(\&_before_delete_invalidate_requirement_spec_version);
46
47 sub _before_save_create_fb_number {
48   my ($self) = @_;
49
50   return 1 if  $self->fb_number;
51   return 0 if !$self->requirement_spec_id;
52
53   my $method      = 'previous_' . ($self->parent_id ? 'fb' : 'section') . '_number';
54   my $next_number = $self->requirement_spec->$method + 1;
55
56   $self->requirement_spec->update_attributes($method => $next_number) || return 0;
57
58   $method    = 'requirement_spec_' . ($self->parent_id ? 'function_block' : 'section') . '_number_format';
59   my $format = SL::DB::Default->get->$method;
60
61   $self->fb_number(SL::PrefixedNumber->new(number => $format || 0)->set_to($next_number));
62
63   return 1;
64 }
65
66 sub _before_save_invalidate_requirement_spec_version {
67   my ($self, %params) = @_;
68
69   return 1 if !$self->requirement_spec_id;
70
71   my %changed_columns = map { $_ => 1 } (Rose::DB::Object::Helpers::dirty_columns($self));
72   my $has_changed     = !Rose::DB::Object::Util::is_in_db($self);
73   $has_changed      ||= any { $changed_columns{$_} } qw(requirement_spec_id parent_id position fb_number title description);
74
75   if (!$has_changed && $self->id) {
76     my $old_item = SL::DB::RequirementSpecItem->new(id => $self->id)->load;
77     $has_changed = join(':', sort map { $_->id } @{ $self->dependencies }) ne join(':', sort map { $_->id } @{ $old_item->dependencies });
78   }
79
80   $self->requirement_spec->invalidate_version if $has_changed;
81
82   return 1;
83 }
84
85 sub _before_delete_invalidate_requirement_spec_version {
86   my ($self, %params) = @_;
87
88   $self->requirement_spec->invalidate_version if $self->requirement_spec_id;
89
90   return 1;
91 }
92
93 sub validate {
94   my ($self) = @_;
95
96   my @errors;
97   push @errors, t8('The title is missing.') if !$self->parent_id && !$self->title;
98
99   return @errors;
100 }
101
102 sub children_sorted {
103   my ($self, @args) = @_;
104
105   croak "Not a writer" if @args;
106
107   return [ sort { $a->position <=> $b->position } $self->children ];
108 }
109
110 sub section {
111   my ($self, @args) = @_;
112
113   croak "Not a writer" if @args;
114   $self = $self->parent while $self->parent_id;
115
116   return $self;
117 }
118
119 sub child_type {
120   my ($self, @args) = @_;
121
122   croak "Not a writer" if @args;
123
124   return $self->item_type eq 'section' ? 'function-block' : 'sub-function-block';
125 }
126
127 1;
128 __END__
129
130 =pod
131
132 =encoding utf8
133
134 =head1 NAME
135
136 SL::DB::RequirementSpecItem - Items for requirement specs
137
138 =head1 OVERVIEW
139
140 Please see L<SL::DB::RequirementSpec> for the architectual overview.
141
142 =head1 FUNCTIONS
143
144 =over 4
145
146 =item C<child_type>
147
148 Returns the C<item_type> for children of C<$self>.
149
150 =item C<children_sorted>
151
152 Returns an array reference of direct children (not of grandchildren)
153 for C<$self> ordered by their positional column in ascending order.
154
155 =item C<section>
156
157 Returns the section this item belongs to. It can be C<$self> if
158 C<$self> is already a section, its parent or grandparent.
159
160 =item C<validate>
161
162 Validates before saving and returns an array of human-readable error
163 messages in case of an error.
164
165 =back
166
167 =head1 BUGS
168
169 Nothing here yet.
170
171 =head1 AUTHOR
172
173 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
174
175 =cut