14 |
14 |
use SL::File;
|
15 |
15 |
use SL::MIME;
|
16 |
16 |
use SL::YAML;
|
|
17 |
use SL::DBUtils qw(selectall_hashref_query);
|
17 |
18 |
use SL::DB::History;
|
18 |
19 |
use SL::DB::Order;
|
19 |
20 |
use SL::DB::Default;
|
... | ... | |
1053 |
1054 |
}
|
1054 |
1055 |
|
1055 |
1056 |
sub action_transfer_stock {
|
1056 |
|
my ($self) = @_;
|
|
1057 |
my ($self, $default_transfer) = @_;
|
1057 |
1058 |
|
1058 |
1059 |
if ($self->order->delivered) {
|
1059 |
1060 |
return $self->js->flash("error",
|
... | ... | |
1084 |
1085 |
$transfer->oe_id($order->id);
|
1085 |
1086 |
$transfer->qty($transfer->qty * -1) if $inout eq 'out';
|
1086 |
1087 |
$transfer->qty($transfer->qty * 1) if $inout eq 'in';
|
|
1088 |
$transfer->comment(t8("Default transfer delivery order")) if $default_transfer;
|
1087 |
1089 |
|
1088 |
1090 |
push @transfer_requests, $transfer if defined $transfer->qty && $transfer->qty != 0;
|
1089 |
1091 |
};
|
... | ... | |
1097 |
1099 |
$_->save for @transfer_requests;
|
1098 |
1100 |
$self->order->update_attributes(delivered => 1, closed => 1);
|
1099 |
1101 |
});
|
1100 |
|
# update stock info (set new delivery_order_items_stock_id)
|
|
1102 |
# update qty and stock info
|
1101 |
1103 |
foreach my $item (@{$self->order->items}) {
|
1102 |
1104 |
$self->order->prepare_stock_info($item);
|
|
1105 |
my $stock_info_yaml = $item->{stock_info};
|
1103 |
1106 |
my $item_position = $item->position;
|
|
1107 |
my $stock_qty = $self->calculate_stock_in_out($item);
|
|
1108 |
my $unit = $item->unit;
|
|
1109 |
$self->js->text("[data-position=$item_position] .data-stock-qty", "$stock_qty $unit");
|
1104 |
1110 |
my $selector = "[data-position=$item_position] .data-stock-info";
|
1105 |
|
$self->js->val($selector, $item->{stock_info});
|
|
1111 |
$self->js->val($selector, $stock_info_yaml);
|
1106 |
1112 |
}
|
1107 |
1113 |
|
1108 |
1114 |
$self->js
|
... | ... | |
1111 |
1117 |
t8('This record has already been delivered.'))
|
1112 |
1118 |
->run('kivi.ActionBar.setDisabled', '#transfer_out_action',
|
1113 |
1119 |
t8('The parts for this order have already been transferred'))
|
|
1120 |
->run('kivi.ActionBar.setDisabled', '#transfer_out_default_action',
|
|
1121 |
t8('The parts for this order have already been transferred'))
|
1114 |
1122 |
->run('kivi.ActionBar.setDisabled', '#transfer_in_action',
|
1115 |
1123 |
t8('The parts for this order have already been transferred'))
|
|
1124 |
->run('kivi.ActionBar.setDisabled', '#transfer_in_default_action',
|
|
1125 |
t8('The parts for this order have already been transferred'))
|
1116 |
1126 |
->run('kivi.ActionBar.setDisabled', '#delete_action',
|
1117 |
1127 |
t8('The parts for this order have already been transferred'))
|
1118 |
1128 |
->run('kivi.ActionBar.setEnabled', '#undo_transfer_action',
|
... | ... | |
1121 |
1131 |
->render;
|
1122 |
1132 |
}
|
1123 |
1133 |
|
|
1134 |
sub action_transfer_stock_default {
|
|
1135 |
my ($self) = @_;
|
|
1136 |
my $delivery_order = $self->order;
|
|
1137 |
my @items = @{$delivery_order->items_sorted};
|
|
1138 |
|
|
1139 |
# get default bin if set in config
|
|
1140 |
my ($default_warehouse_id, $default_bin_id);
|
|
1141 |
if ($::instance_conf->get_transfer_default_use_master_default_bin) {
|
|
1142 |
$default_warehouse_id = $::instance_conf->get_warehouse_id;
|
|
1143 |
$default_bin_id = $::instance_conf->get_bin_id;
|
|
1144 |
}
|
|
1145 |
|
|
1146 |
my @transfer_requests = ();
|
|
1147 |
my %parts_qty = ();
|
|
1148 |
my %units_by_name = map { $_->name => $_ } @{ SL::DB::Manager::Unit->get_all };
|
|
1149 |
foreach my $item (@items) {
|
|
1150 |
my $part = $item->part;
|
|
1151 |
my $base_unit_factor = $units_by_name{$part->unit}->factor || 1;
|
|
1152 |
my $item_unit_factor = $units_by_name{$item->unit}->factor || 1;
|
|
1153 |
my $qty = $item->qty * $item_unit_factor / $base_unit_factor;
|
|
1154 |
return $self->js->flash('error', t8('Cannot transfer negative entries.'))->render() if $qty < 0;
|
|
1155 |
$qty = 0 if (!$::instance_conf->get_transfer_default_services && $part->is_service);
|
|
1156 |
|
|
1157 |
$parts_qty{$part->id} += $qty if $qty;
|
|
1158 |
push @transfer_requests, {
|
|
1159 |
'delivery_order_item_id' => $item->id,
|
|
1160 |
'warehouse_id' => $part->warehouse_id || $default_warehouse_id,
|
|
1161 |
'bin_id' => $part->bin_id || $default_bin_id,
|
|
1162 |
'unit' => $part->unit,
|
|
1163 |
'qty' => $qty,
|
|
1164 |
# added in check transfer_request out direction if possible
|
|
1165 |
'chargenumber' => undef, # $item->serialnumber, # Is not used in delivery order
|
|
1166 |
'bestbefore' => undef, # $item->bestbefore, # Is not used in delivery order
|
|
1167 |
}
|
|
1168 |
}
|
|
1169 |
|
|
1170 |
# check transfer_requests are correctly
|
|
1171 |
my %parts_errors = (); # missing_bin, missing_qty, multiple_options
|
|
1172 |
my $grouped_qty_query = qq|
|
|
1173 |
SELECT SUM(qty) as qty, chargenumber, bestbefore
|
|
1174 |
FROM inventory
|
|
1175 |
WHERE parts_id = ? AND bin_id = ?
|
|
1176 |
GROUP BY chargenumber, bestbefore
|
|
1177 |
|;
|
|
1178 |
my $dbh = $self->order->dbh;
|
|
1179 |
my $in_out_direction = $delivery_order->type_data->properties('transfer');
|
|
1180 |
for my $idx (0 .. scalar @transfer_requests - 1) {
|
|
1181 |
my $transfer_request = $transfer_requests[$idx];
|
|
1182 |
next unless $transfer_request->{qty}; # empty request
|
|
1183 |
my $item = $items[$idx];
|
|
1184 |
my $part_id = $item->parts_id;
|
|
1185 |
my $bin_id = $transfer_request->{bin_id};
|
|
1186 |
$parts_errors{$part_id}{missing_bin} = 1 unless $bin_id;
|
|
1187 |
next unless $bin_id;
|
|
1188 |
if ($in_out_direction eq 'out') {
|
|
1189 |
my @grouped_qty = selectall_hashref_query(
|
|
1190 |
$::form, $dbh, $grouped_qty_query, $part_id, $bin_id);
|
|
1191 |
|
|
1192 |
if (1 < scalar grep {$_->{qty} != 0} @grouped_qty) {
|
|
1193 |
$parts_errors{$part_id}{multiple_options} = 1;
|
|
1194 |
}
|
|
1195 |
my $max_qty = sum0(map {$_->{qty}} @grouped_qty);
|
|
1196 |
if ($max_qty < $parts_qty{$part_id}) {
|
|
1197 |
$parts_errors{$part_id}{missing_qty} = $parts_qty{$part_id} - $max_qty;
|
|
1198 |
}
|
|
1199 |
|
|
1200 |
next if $parts_errors{$part_id};
|
|
1201 |
# find correct chargenumber and bestbefore
|
|
1202 |
my $stock_info = first {$_->{qty} >= $transfer_request->{qty}} @grouped_qty;
|
|
1203 |
$transfer_request->{chargenumber} = $stock_info->{chargenumber};
|
|
1204 |
$transfer_request->{bestbefore} = $stock_info->{bestbefore};
|
|
1205 |
}
|
|
1206 |
}
|
|
1207 |
|
|
1208 |
# auslagern soll immer gehen, auch wenn nicht genügend auf lager ist.
|
|
1209 |
# der lagerplatz ist hier extra konfigurierbar, bspw. Lager-Korrektur mit
|
|
1210 |
# Lagerplatz Lagerplatz-Korrektur
|
|
1211 |
my $default_warehouse_id_ignore_onhand = $::instance_conf->get_warehouse_id_ignore_onhand;
|
|
1212 |
my $default_bin_id_ignore_onhand = $::instance_conf->get_bin_id_ignore_onhand;
|
|
1213 |
if ($::instance_conf->get_transfer_default_ignore_onhand && $default_bin_id_ignore_onhand) {
|
|
1214 |
foreach my $part_id (keys %parts_errors) {
|
|
1215 |
# entsprechende defaults holen
|
|
1216 |
# falls chargenumber, bestbefore oder anzahl nicht stimmt, auf automatischen
|
|
1217 |
# lagerplatz wegbuchen!
|
|
1218 |
foreach (@transfer_requests) {
|
|
1219 |
if ($_->{delivery_order_item}->parts_id eq $part_id){
|
|
1220 |
$_->{bin_id} = $default_bin_id_ignore_onhand;
|
|
1221 |
$_->{warehouse_id} = $default_warehouse_id_ignore_onhand;
|
|
1222 |
}
|
|
1223 |
}
|
|
1224 |
delete %parts_errors{$part_id};
|
|
1225 |
}
|
|
1226 |
}
|
|
1227 |
|
|
1228 |
# render errors
|
|
1229 |
if (scalar keys %parts_errors) {
|
|
1230 |
my @multiple_options = ();
|
|
1231 |
foreach my $part_id (keys %parts_errors) {
|
|
1232 |
my $part = SL::DB::Part->new(id => $part_id)->load();
|
|
1233 |
if ($parts_errors{$part_id}{missing_bin}){
|
|
1234 |
$self->js->error(t8('No standard bin set for #1.', $part->displayable_name));
|
|
1235 |
}
|
|
1236 |
if ($parts_errors{$part_id}{missing_qty}) {
|
|
1237 |
$self->js->error(
|
|
1238 |
t8('There are #1 of "#2" missing from the standard bin #3 for transfer.',
|
|
1239 |
$parts_errors{$part_id}{missing_qty}, $part->displayable_name, $part->bin->full_description));
|
|
1240 |
}
|
|
1241 |
if ($parts_errors{$part_id}{multiple_options}){
|
|
1242 |
push @multiple_options, $part;
|
|
1243 |
}
|
|
1244 |
}
|
|
1245 |
if (scalar @multiple_options) {
|
|
1246 |
$self->js->error(t8(
|
|
1247 |
"There are parts with multiple chargenumbers or bestbefore dates set. This can't be decided automatically. Pleas transfer this delivery order manually. Can't decided for #1.",
|
|
1248 |
join ", ", map {$_->displayable_name} @multiple_options)
|
|
1249 |
);
|
|
1250 |
}
|
|
1251 |
return $self->js->render();
|
|
1252 |
}
|
|
1253 |
|
|
1254 |
# assign each delivery_order_item it's stock
|
|
1255 |
for my $idx (0 .. scalar @transfer_requests - 1) {
|
|
1256 |
my %transfer_request = %{$transfer_requests[$idx]};
|
|
1257 |
next unless $transfer_request{qty}; # empty request
|
|
1258 |
|
|
1259 |
my $item = $items[$idx];
|
|
1260 |
my @stocks = (SL::DB::DeliveryOrderItemsStock->new(%transfer_request));
|
|
1261 |
$item->delivery_order_stock_entries(@stocks);
|
|
1262 |
}
|
|
1263 |
|
|
1264 |
my $default_transfer = 1;
|
|
1265 |
$self->action_transfer_stock($default_transfer);
|
|
1266 |
}
|
|
1267 |
|
1124 |
1268 |
sub action_undo_transfers {
|
1125 |
1269 |
my ( $self ) = @_;
|
1126 |
1270 |
|
... | ... | |
1892 |
2036 |
only_if => $self->type_data->properties('transfer') eq 'out',
|
1893 |
2037 |
confirm => t8('Do you really want to transfer the stock and set this order to delivered?'),
|
1894 |
2038 |
],
|
|
2039 |
action => [
|
|
2040 |
t8('Transfer out via default'),
|
|
2041 |
id => 'transfer_out_default_action',
|
|
2042 |
call => [ 'kivi.DeliveryOrder.save', {
|
|
2043 |
action => 'transfer_stock_default',
|
|
2044 |
}],
|
|
2045 |
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.')
|
|
2046 |
: !$self->order->id ? t8('This object has not been saved yet.')
|
|
2047 |
: $self->order->delivered ? t8('The parts for this order have already been transferred')
|
|
2048 |
: undef,
|
|
2049 |
only_if => $self->type_data->properties('transfer') eq 'out',
|
|
2050 |
confirm => t8('Do you really want to transfer the stock and set this order to delivered?'),
|
|
2051 |
],
|
1895 |
2052 |
action => [
|
1896 |
2053 |
t8('Transfer in'),
|
1897 |
2054 |
id => 'transfer_in_action',
|
... | ... | |
1905 |
2062 |
only_if => $self->type_data->properties('transfer') eq 'in',
|
1906 |
2063 |
confirm => t8('Do you really want to transfer the stock and set this order to delivered?'),
|
1907 |
2064 |
],
|
|
2065 |
action => [
|
|
2066 |
t8('Transfer in via default'),
|
|
2067 |
id => 'transfer_in_default_action',
|
|
2068 |
call => [ 'kivi.DeliveryOrder.save', {
|
|
2069 |
action => 'transfer_stock_default',
|
|
2070 |
}],
|
|
2071 |
disabled => !$may_edit_create ? t8('You do not have the permissions to access this function.')
|
|
2072 |
: !$self->order->id ? t8('This object has not been saved yet.')
|
|
2073 |
: $self->order->delivered ? t8('The parts for this order have already been transferred')
|
|
2074 |
: undef,
|
|
2075 |
only_if => $self->type_data->properties('transfer') eq 'in',
|
|
2076 |
confirm => t8('Do you really want to transfer the stock and set this order to delivered?'),
|
|
2077 |
],
|
1908 |
2078 |
action => [
|
1909 |
2079 |
t8('Undo Transfer'),
|
1910 |
2080 |
id => 'undo_transfer_action',
|
DeliveryOrder: Ein-/Auslagern über Standard-Lagerplatz