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