+sub get_mini_journal {
+ my ($self) = @_;
+
+ # inventory ids of the most recent 10 inventory trans_ids
+
+ # duplicate code copied from SL::Controller::Inventory mini_journal, except
+ # for the added filter on parts_id
+
+ my $parts_id = $self->id;
+ my $query = <<"SQL";
+with last_inventories as (
+ select id,
+ trans_id,
+ itime
+ from inventory
+ where parts_id = $parts_id
+ order by itime desc
+ limit 20
+),
+grouped_ids as (
+ select trans_id,
+ array_agg(id) as ids
+ from last_inventories
+ group by trans_id
+ order by max(itime)
+ desc limit 10
+)
+select unnest(ids)
+ from grouped_ids
+ limit 20 -- so the planner knows how many ids to expect, the cte is an optimisation fence
+SQL
+
+ my $objs = SL::DB::Manager::Inventory->get_all(
+ query => [ id => [ \"$query" ] ], # make emacs happy "
+ with_objects => [ 'parts', 'trans_type', 'bin', 'bin.warehouse' ], # prevent lazy loading in template
+ sort_by => 'itime DESC',
+ );
+ # remember order of trans_ids from query, for ordering hash later
+ my @sorted_trans_ids = uniq map { $_->trans_id } @$objs;
+
+ # at most 2 of them belong to a transaction and the qty determines in or out.
+ my %transactions;
+ for (@$objs) {
+ $transactions{ $_->trans_id }{ $_->qty > 0 ? 'in' : 'out' } = $_;
+ $transactions{ $_->trans_id }{base} = $_;
+ }
+
+ # because the inventory transactions were built in a hash, we need to sort the
+ # hash by using the original sort order of the trans_ids
+ my @sorted = map { $transactions{$_} } @sorted_trans_ids;
+
+ return \@sorted;
+}
+
+sub clone_and_reset_deep {
+ my ($self) = @_;
+
+ my $clone = $self->clone_and_reset; # resets id and partnumber (primary key and unique constraint)
+ $clone->makemodels( map { $_->clone_and_reset } @{$self->makemodels} ) if @{$self->makemodels};
+ $clone->translations( map { $_->clone_and_reset } @{$self->translations} ) if @{$self->translations};
+
+ if ( $self->is_assortment ) {
+ # use clone rather than reset_and_clone because the unique constraint would also remove parts_id
+ $clone->assortment_items( map { $_->clone } @{$self->assortment_items} );
+ $_->assortment_id(undef) foreach @{ $clone->assortment_items }
+ };
+
+ if ( $self->is_assembly ) {
+ $clone->assemblies( map { $_->clone_and_reset } @{$self->assemblies});
+ };
+
+ if ( $self->prices ) {
+ $clone->prices( map { $_->clone } @{$self->prices}); # pricegroup_id gets reset here because it is part of a unique contraint
+ if ( $clone->prices ) {
+ foreach my $price ( @{$clone->prices} ) {
+ $price->id(undef);
+ $price->parts_id(undef);
+ };
+ };
+ };
+
+ return $clone;
+}
+
+sub item_diffs {
+ my ($self, $comparison_part) = @_;
+
+ die "item_diffs needs a part object" unless ref($comparison_part) eq 'SL::DB::Part';
+ die "part and comparison_part need to be of the same part_type" unless
+ ( $self->part_type eq 'assembly' or $self->part_type eq 'assortment' )
+ and ( $comparison_part->part_type eq 'assembly' or $comparison_part->part_type eq 'assortment' )
+ and $self->part_type eq $comparison_part->part_type;
+
+ # return [], [] if $self->items_checksum eq $comparison_part->items_checksum;
+ my @self_part_ids = map { $_->parts_id } $self->items;
+ my @comparison_part_ids = map { $_->parts_id } $comparison_part->items;
+
+ my %orig = map{ $_ => 1 } @self_part_ids;
+ my %comparison = map{ $_ => 1 } @comparison_part_ids;
+ my (@additions, @removals);
+ @additions = grep { !exists( $orig{$_} ) } @comparison_part_ids if @comparison_part_ids;
+ @removals = grep { !exists( $comparison{$_} ) } @self_part_ids if @self_part_ids;
+
+ return \@additions, \@removals;
+};
+
+sub items_sellprice_sum {
+ my ($self, %params) = @_;
+
+ return unless $self->is_assortment or $self->is_assembly;
+ return unless $self->items;
+
+ if ($self->is_assembly) {
+ return sum map { $_->linetotal_sellprice } @{$self->items};
+ } else {
+ return sum map { $_->linetotal_sellprice(%params) } grep { $_->charge } @{$self->items};
+ }
+}
+
+sub items_lastcost_sum {
+ my ($self) = @_;
+
+ return unless $self->is_assortment or $self->is_assembly;
+ return unless $self->items;
+ sum map { $_->linetotal_lastcost } @{$self->items};
+};
+
+sub items_weight_sum {
+ my ($self) = @_;
+
+ return unless $self->is_assembly;
+ return unless $self->items;
+ sum map { $_->linetotal_weight} @{$self->items};
+};
+