From: Janusz Dobrowolski Date: Sat, 20 Sep 2014 22:09:59 +0000 (+0200) Subject: Fixed stock quantity checks to block transactions which would result in negative... X-Git-Tag: 2.3-final~107 X-Git-Url: https://delta.frontaccounting.com/gitweb/?p=fa-stable.git;a=commitdiff_plain;h=ec51d9a922df61d39de8444cd0a3aaf51ebcbf2b Fixed stock quantity checks to block transactions which would result in negative inventory status (if not allowed). --- diff --git a/gl/includes/db/gl_db_bank_trans.inc b/gl/includes/db/gl_db_bank_trans.inc index f8880152..62900fc3 100644 --- a/gl/includes/db/gl_db_bank_trans.inc +++ b/gl/includes/db/gl_db_bank_trans.inc @@ -177,8 +177,8 @@ function check_bank_account_history($delta_amount, $bank_account, $date=null, $u if (!isset($balance) && isset($date)) return null; // unlimited account - if ($balance < -$delta_amount) - return array('amount' => $balance, 'trans_date'=> $date); + if (floatcmp($balance, -$delta_amount) < 0) + return array('amount' => $balance - $delta_amount, 'trans_date'=> $date); $balance += $delta_amount; diff --git a/includes/db/inventory_db.inc b/includes/db/inventory_db.inc index e8f8e3c2..96d7b8ac 100644 --- a/includes/db/inventory_db.inc +++ b/includes/db/inventory_db.inc @@ -9,46 +9,77 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License here . ***********************************************************************/ -function get_qoh_on_date($stock_id, $location=null, $date_=null, $exclude=0) +function get_qoh_on_date($stock_id, $location=null, $date_=null) { if ($date_ == null) - { - $sql = "SELECT SUM(qty) FROM ".TB_PREF."stock_moves - WHERE stock_id=".db_escape($stock_id); $date_ = Today(); - $date = date2sql($date_); - } - else - { - $date = date2sql($date_); - $sql = "SELECT SUM(qty) FROM ".TB_PREF."stock_moves + + $date = date2sql($date_); + + $sql = "SELECT SUM(qty) FROM ".TB_PREF."stock_moves WHERE stock_id=".db_escape($stock_id)." AND tran_date <= '$date'"; - } - + if ($location != null) $sql .= " AND loc_code = ".db_escape($location); - $result = db_query($sql, "QOH calulcation failed"); + $result = db_query($sql, "QOH calculation failed"); $myrow = db_fetch_row($result); - if ($exclude > 0) - { - $sql = "SELECT SUM(qty) FROM ".TB_PREF."stock_moves - WHERE stock_id=".db_escape($stock_id) - ." AND type=".db_escape($exclude) - ." AND tran_date = '$date'"; - - $result = db_query($sql, "QOH calulcation failed"); - $myrow2 = db_fetch_row($result); - if ($myrow2 !== false) - $myrow[0] -= $myrow2[0]; - } $qoh = $myrow[0]; return $qoh ? $qoh : 0; } +/** +* Check whether change in stock on date would not cause negative qoh in stock history. +* Returns null on success or max. available quantity with respective date otherwise. +* Running balance is checked on daily basis only, as we do not control time of transaction. +* +* $delta_qty - tested change in stock qty at $date. +* $date - check date; when set to null checks all the stock history. +**/ + +function check_negative_stock($stock_id, $delta_qty, $location=null, $date=null) +{ + + if ($delta_qty >= 0) + return null; // qty increese is always safe + + if (!isset($date)) + $date = Today(); + + $date = date2sql($date); + + // check stock status on date + $sql = "SELECT SUM(qty) qty, '$date' tran_date FROM ".TB_PREF."stock_moves + WHERE stock_id=".db_escape($stock_id)." + AND tran_date <= '$date'"; + + if ($location) + $sql .= " AND loc_code = ".db_escape($location); + + $result = db_query($sql, "QOH calculation failed"); + $qos = db_fetch_assoc($result); + + // check also all stock changes after the date to avoid negative stock in future + $sql = TB_PREF."stock_moves WHERE stock_id=".db_escape($stock_id) . " AND tran_date > '$date'"; + + if ($location) + $sql .= " AND loc_code=".db_escape($location); + + $rt = running_total_sql($sql, 'qty', 'tran_date'); + + $sql = "SELECT {$qos['qty']}+total qty, tran_date FROM ($rt) stock_status ORDER by total, tran_date"; + $history = db_query($sql, 'cannot check stock history'); + $min_qos = db_fetch($history); + + if ($min_qos && ($min_qos['qty'] < $qos['qty'])) + $qos = $min_qos; + + return -$delta_qty > $qos['qty'] ? $qos : null; +} + //-------------------------------------------------------------------------------------- function get_item_edit_info($stock_id) diff --git a/includes/db/manufacturing_db.inc b/includes/db/manufacturing_db.inc index ddb93c58..bfd40704 100644 --- a/includes/db/manufacturing_db.inc +++ b/includes/db/manufacturing_db.inc @@ -41,7 +41,7 @@ function load_stock_levels($location) $sql = "SELECT stock_id, SUM(qty) FROM ".TB_PREF."stock_moves WHERE tran_date <= '$date'"; if ($location != '') $sql .= " AND loc_code = ".db_escape($location); $sql .= " GROUP BY stock_id"; - $result = db_query($sql, "QOH calulcation failed"); + $result = db_query($sql, "QOH calculation failed"); while ($row = db_fetch($result)) { $qoh_stock[$row[0]] = $row[1]; } diff --git a/includes/db/sql_functions.inc b/includes/db/sql_functions.inc index febc3af8..aead5d37 100644 --- a/includes/db/sql_functions.inc +++ b/includes/db/sql_functions.inc @@ -59,4 +59,21 @@ function update_record_status($id, $status, $table, $key) { db_query($sql, "Can't update record status"); } -?> +//----------------------------------------------------------------------------- +// +// Helper for sql subquery returning running totals from delta tables like stock_moves or bank_trans +// +// $table - table name with optional WHERE clause +// $column - delta column +// $index - comma delimited list of columns for total grouping and order +// Returns running totals with respective index column +// +function running_total_sql($table, $column, $index) +{ + + return "SELECT daily.$index, daily.$column, (@total:=@total+daily.$column) total + FROM + (SELECT $index, sum($column) $column FROM $table GROUP BY $index ORDER BY $index) daily, + (SELECT @total:=0) total_var"; +} + diff --git a/includes/ui/items_cart.inc b/includes/ui/items_cart.inc index a5e15fdb..bf7570cd 100644 --- a/includes/ui/items_cart.inc +++ b/includes/ui/items_cart.inc @@ -82,15 +82,32 @@ class items_cart return count($this->line_items); } + /* + Checks cart quantities on document_date. + Returns array of stock_ids which stock quantities would go negative on some day. + */ function check_qoh($location, $date_, $reverse=false) { + $low_stock = array(); + + // collect quantities by stock_id + $qtys = array(); foreach ($this->line_items as $line_no => $line_item) { - $item_ret = $line_item->check_qoh($location, $date_, $reverse); - if ($item_ret != null) - return $line_no; + $qty = $reverse ? -$line_item->quantity : $line_item->quantity; + + $qtys[$line_item->stock_id]['qty'] = $qty + @$qtys[$line_item->stock_id]['qty']; + $qtys[$line_item->stock_id]['line'] = $line_no; } - return -1; + + foreach($qtys as $stock_id => $sum) + { + $fail = check_negative_stock($stock_id, $sum['qty'], $location, $date_); + if ($fail) + $low_stock[] = $stock_id; + } + + return $low_stock; } // ----------- GL item functions @@ -218,10 +235,14 @@ class line_item $this->price = 0; } + /* + This method is generally obsolete and subject to removal in FA 2.4 (preserved for now to support 2.3 extensions). + Use items_cart::check_qoh instead. + */ function check_qoh($location, $date_, $reverse) { global $SysPrefs; - + if (!$SysPrefs->allow_negative_stock()) { if (has_stock_holding($this->mb_flag)) @@ -233,11 +254,9 @@ class line_item if ($quantity >= 0) return null; - $qoh = get_qoh_on_date($this->stock_id, $location, $date_); - if ($quantity + $qoh < 0) - { - return $this; - } + $fail = check_negative_stock($this->stock_id, $quantity, $location, $date_); + if ($fail) + return $this; } } diff --git a/inventory/adjustments.php b/inventory/adjustments.php index ea6d41ba..dc14d93e 100644 --- a/inventory/adjustments.php +++ b/inventory/adjustments.php @@ -80,7 +80,7 @@ function handle_new_order() function can_process() { - global $Refs; + global $Refs, $SysPrefs; $adj = &$_SESSION['adj_items']; @@ -114,16 +114,16 @@ function can_process() display_error(_("The entered date is not in fiscal year.")); set_focus('AdjDate'); return false; - } else { - $failed_item = $adj->check_qoh($_POST['StockLocation'], $_POST['AdjDate'], !$_POST['Increase']); - if ($failed_item >= 0) + } + elseif (!$SysPrefs->allow_negative_stock()) + { + $low_stock = $adj->check_qoh($_POST['StockLocation'], $_POST['AdjDate'], !$_POST['Increase']); + + if ($low_stock) { - $line = $adj->line_items[$failed_item]; - display_error(_("The adjustment cannot be processed because an adjustment item would cause a negative inventory balance :") . - " " . $line->stock_id . " - " . $line->item_description); - $_POST['Edit'.$failed_item] = 1; // enter edit mode + display_error(_("The adjustment cannot be processed because it would cause negative inventory balance for marked items as of document date or later.")); unset($_POST['Process']); - return false; + return false; } } return true; @@ -141,6 +141,7 @@ if (isset($_POST['Process']) && can_process()){ unset($_SESSION['adj_items']); meta_forward($_SERVER['PHP_SELF'], "AddedID=$trans_no"); + } /*end of process credit note */ //----------------------------------------------------------------------------------------------- diff --git a/inventory/includes/item_adjustments_ui.inc b/inventory/includes/item_adjustments_ui.inc index f7af3dad..12571b8a 100644 --- a/inventory/includes/item_adjustments_ui.inc +++ b/inventory/includes/item_adjustments_ui.inc @@ -17,7 +17,7 @@ include_once($path_to_root . "/includes/ui/items_cart.inc"); function add_to_order(&$order, $new_item, $new_item_qty, $standard_cost) { if ($order->find_cart_item($new_item)) - display_error(_("For Part :") . $new_item . " " . "This item is already on this order. You can change the quantity ordered of the existing line if necessary."); + display_error(_("For Part :") . $new_item . " " . "This item is already on this document. You can change the quantity on the existing line if necessary."); else $order->add_to_cart (count($order->line_items), $new_item, $new_item_qty, $standard_cost); } @@ -67,6 +67,7 @@ function display_adjustment_items($title, &$order) $total = 0; $k = 0; //row colour counter + $low_stock = $order->check_qoh($_POST['StockLocation'], $_POST['AdjDate'], !$_POST['Increase']); $id = find_submit('Edit'); foreach ($order->line_items as $line_no=>$stock_item) { @@ -75,7 +76,10 @@ function display_adjustment_items($title, &$order) if ($id != $line_no) { - alt_table_row_color($k); + if (in_array($stock_item->stock_id, $low_stock)) + start_row("class='stockmankobg'"); // notice low stock status + else + alt_table_row_color($k); view_stock_status_cell($stock_item->stock_id); label_cell($stock_item->item_description); @@ -102,6 +106,8 @@ function display_adjustment_items($title, &$order) label_row(_("Total"), number_format2($total,user_price_dec()), "align=right colspan=5", "align=right", 2); end_table(); + if ($low_stock) + display_note(_("Marked items have insufficient quantities in stock as on day of adjustment."), 0, 1, "class='stockmankofg'"); div_end(); } diff --git a/inventory/includes/stock_transfers_ui.inc b/inventory/includes/stock_transfers_ui.inc index 474fc9df..026e0b6e 100644 --- a/inventory/includes/stock_transfers_ui.inc +++ b/inventory/includes/stock_transfers_ui.inc @@ -17,7 +17,7 @@ include_once($path_to_root . "/includes/ui/items_cart.inc"); function add_to_order(&$order, $new_item, $new_item_qty, $standard_cost) { if ($order->find_cart_item($new_item)) - display_error(_("For Part :") . $new_item . " " . "This item is already on this order. You can change the quantity ordered of the existing line if necessary."); + display_error(_("For Part :") . $new_item . " " . "This item is already on this document. You can change the quantity on the existing line if necessary."); else $order->add_to_cart (count($order->line_items), $new_item, $new_item_qty, $standard_cost); } @@ -63,13 +63,17 @@ function display_transfer_items($title, &$order) $subtotal = 0; $k = 0; //row colour counter + $low_stock = $order->check_qoh($_POST['FromStockLocation'], $_POST['AdjDate'], true); $id = find_submit('Edit'); foreach ($order->line_items as $line_no=>$stock_item) { if ($id != $line_no) { - alt_table_row_color($k); + if (in_array($stock_item->stock_id, $low_stock)) + start_row("class='stockmankobg'"); // notice low stock status + else + alt_table_row_color($k); view_stock_status_cell($stock_item->stock_id); label_cell($stock_item->item_description); @@ -92,6 +96,8 @@ function display_transfer_items($title, &$order) transfer_edit_item_controls($order); end_table(); + if ($low_stock) + display_note(_("Marked items have insufficient quantities in stock as on day of transfer."), 0, 1, "class='stockmankofg'"); div_end(); } diff --git a/inventory/transfers.php b/inventory/transfers.php index 4ad32ea2..b04c7d87 100644 --- a/inventory/transfers.php +++ b/inventory/transfers.php @@ -115,17 +115,14 @@ if (isset($_POST['Process'])) display_error(_("The locations to transfer from and to must be different.")); set_focus('FromStockLocation'); $input_error = 1; - } - else + } + elseif (!$SysPrefs->allow_negative_stock()) { - $failed_item = $tr->check_qoh($_POST['FromStockLocation'], $_POST['AdjDate'], true); - if ($failed_item >= 0) + $low_stock = $tr->check_qoh($_POST['FromStockLocation'], $_POST['AdjDate'], true); + + if ($low_stock) { - $line = $tr->line_items[$failed_item]; - display_error(_("The quantity entered is greater than the available quantity for this item at the source location :") . - " " . $line->stock_id . " - " . $line->item_description); - echo "
"; - $_POST['Edit'.$failed_item] = 1; // enter edit mode + display_error(_("The transfer cannot be processed because it would cause negative inventory balance in source location for marked items as of document date or later.")); $input_error = 1; } } diff --git a/manufacturing/includes/work_order_issue_ui.inc b/manufacturing/includes/work_order_issue_ui.inc index 7a2baacb..e70ce571 100644 --- a/manufacturing/includes/work_order_issue_ui.inc +++ b/manufacturing/includes/work_order_issue_ui.inc @@ -38,6 +38,8 @@ function display_issue_items($title, &$order) // $total = 0; $k = 0; //row colour counter + if (count($order->line_items)) + $low_stock = $order->check_qoh($_POST['Location'], $_POST['date_'], !$_POST['IssueType']); $id = find_submit('Edit'); foreach ($order->line_items as $line_no=>$stock_item) { @@ -46,7 +48,10 @@ function display_issue_items($title, &$order) if ($id != $line_no) { - alt_table_row_color($k); + if (in_array($stock_item->stock_id, $low_stock)) + start_row("class='stockmankobg'"); // notice low stock status + else + alt_table_row_color($k); view_stock_status_cell($stock_item->stock_id); label_cell($stock_item->item_description); @@ -73,6 +78,8 @@ function display_issue_items($title, &$order) // label_row(_("Total"), number_format2($total,user_price_dec()), "colspan=5", "align=right"); end_table(); + if (@$low_stock) + display_note(_("Marked items have insufficient quantities in stock as on day of issue."), 0, 1, "class='stockmankofg'"); div_end(); } diff --git a/manufacturing/work_order_add_finished.php b/manufacturing/work_order_add_finished.php index acf67f73..fb2ae94c 100644 --- a/manufacturing/work_order_add_finished.php +++ b/manufacturing/work_order_add_finished.php @@ -121,10 +121,7 @@ function can_process() // if unassembling we need to check the qoh if (($_POST['ProductionType'] == 0) && !$SysPrefs->allow_negative_stock()) { - $wo_details = get_work_order($_POST['selected_id']); - - $qoh = get_qoh_on_date($wo_details["stock_id"], $wo_details["loc_code"], $_POST['date_']); - if (-input_num('quantity') + $qoh < 0) + if (check_negative_stock($wo_details["stock_id"], -input_num('quantity'), $wo_details["loc_code"], $_POST['date_'])) { display_error(_("The unassembling cannot be processed because there is insufficient stock.")); set_focus('quantity'); @@ -141,13 +138,13 @@ function can_process() { if ($row['mb_flag'] == 'D') // service, non stock continue; - $qoh = get_qoh_on_date($row["stock_id"], $row["loc_code"], $_POST['date_']); - if ($qoh - $row['units_req'] * input_num('quantity') < 0) + + if (check_negative_stock($row["stock_id"], -$row['units_req'] * input_num('quantity'), $row["loc_code"], $_POST['date_'])) { display_error( _("The production cannot be processed because a required item would cause a negative inventory balance :") . " " . $row['stock_id'] . " - " . $row['description']); - $err = true; - } + $err = true; + } } if ($err) { diff --git a/manufacturing/work_order_entry.php b/manufacturing/work_order_entry.php index f5e90d10..049ab914 100644 --- a/manufacturing/work_order_entry.php +++ b/manufacturing/work_order_entry.php @@ -203,8 +203,7 @@ function can_process() $quantity = $bom_item["quantity"] * input_num('quantity'); - $qoh = get_qoh_on_date($bom_item["component"], $bom_item["loc_code"], $_POST['date_']); - if (-$quantity + $qoh < 0) + if (check_negative_stock($bom_item["component"], -$quantity, $bom_item["loc_code"], $_POST['date_'])) { display_error(_("The work order cannot be processed because there is an insufficient quantity for component:") . " " . $bom_item["component"] . " - " . $bom_item["description"] . ". " . _("Location:") . " " . $bom_item["location_name"]); @@ -217,8 +216,7 @@ function can_process() elseif ($_POST['type'] == WO_UNASSEMBLY) { // if unassembling, check item to unassemble - $qoh = get_qoh_on_date($_POST['stock_id'], $_POST['StockLocation'], $_POST['date_']); - if (-input_num('quantity') + $qoh < 0) + if (check_negative_stock($_POST['stock_id'], -input_num('quantity'), $_POST['StockLocation'], $_POST['date_'])) { display_error(_("The selected item cannot be unassembled because there is insufficient stock.")); return false; diff --git a/manufacturing/work_order_issue.php b/manufacturing/work_order_issue.php index 071bf3c5..cfd4d197 100644 --- a/manufacturing/work_order_issue.php +++ b/manufacturing/work_order_issue.php @@ -101,8 +101,7 @@ function can_process() $failed_item = $_SESSION['issue_items']->check_qoh($_POST['Location'], $_POST['date_'], !$_POST['IssueType']); if ($failed_item != -1) { - display_error( _("The issue cannot be processed because an entered item would cause a negative inventory balance :") . - " " . $failed_item->stock_id . " - " . $failed_item->item_description); + display_error(_("The issue cannot be processed because it would cause negative inventory balance for marked items as of document date or later.")); return false; } diff --git a/purchasing/supplier_credit.php b/purchasing/supplier_credit.php index 57e8696e..b4f31966 100644 --- a/purchasing/supplier_credit.php +++ b/purchasing/supplier_credit.php @@ -204,8 +204,7 @@ function check_data() foreach ($_SESSION['supp_trans']->grn_items as $n => $item) { if (is_inventory_item($item->item_code)) { - $qoh = get_qoh_on_date($item->item_code, null, $_SESSION['supp_trans']->tran_date); - if ($item->this_quantity_inv > $qoh) + if (check_negative_stock($item->item_code, -$item->this_quantity_inv, null, $_SESSION['supp_trans']->tran_date)) { $stock = get_item($item->item_code); display_error(_("The return cannot be processed because there is an insufficient quantity for item:") . diff --git a/sales/customer_delivery.php b/sales/customer_delivery.php index 114f3d4a..36dc0b49 100644 --- a/sales/customer_delivery.php +++ b/sales/customer_delivery.php @@ -141,7 +141,7 @@ if (isset($_GET['OrderNumber']) && $_GET['OrderNumber'] > 0) { function check_data() { - global $Refs; + global $Refs, $SysPrefs; if (!isset($_POST['DispatchDate']) || !is_date($_POST['DispatchDate'])) { display_error(_("The entered date of delivery is invalid.")); @@ -187,6 +187,14 @@ function check_data() return false; } + copy_to_cart(); + + if (!$SysPrefs->allow_negative_stock() && ($low_stock = $_SESSION['Items']->check_qoh())) + { + display_error(_("This document cannot be processed because there is insufficient quantity for: ").implode(',', $low_stock)); + return false; + } + return true; } //------------------------------------------------------------------------------ @@ -236,7 +244,7 @@ function check_quantities() $min = 0; $max = $itm->quantity - $itm->qty_done; } - + if (check_num('Line'.$line, $min, $max)) { $_SESSION['Items']->line_items[$line]->qty_dispatched = input_num('Line'.$line); @@ -261,40 +269,8 @@ function check_quantities() } //------------------------------------------------------------------------------ -function check_qoh() +if (isset($_POST['process_delivery']) && check_data()) { - global $SysPrefs; - $dn = &$_SESSION['Items']; - $newdelivery = ($dn->trans_no==0); - if (!$SysPrefs->allow_negative_stock()) { - foreach ($_SESSION['Items']->line_items as $itm) { - - if ($itm->qty_dispatched && has_stock_holding($itm->mb_flag)) { - $qoh_by_date = get_qoh_on_date($itm->stock_id, $_POST['Location'], $_POST['DispatchDate']); - $qoh_abs = get_qoh_on_date($itm->stock_id, $_POST['Location'], null); - //If editing current delivery delivered qty should be added - if (!$newdelivery) - { - $delivered = get_already_delivered($itm->stock_id, $_POST['Location'], key($dn->trans_no)); - - $qoh_abs = $qoh_abs - $delivered; - $qoh_by_date = $qoh_by_date - $delivered; - } - $qoh = ($qoh_by_date < $qoh_abs ? $qoh_by_date : $qoh_abs); - if ($itm->qty_dispatched > $qoh) { - display_error(_("The delivery cannot be processed because there is an insufficient quantity for item:") . - " " . $itm->stock_id . " - " . $itm->item_description); - return false; - } - } - } - } - return true; -} - -//------------------------------------------------------------------------------ - -if (isset($_POST['process_delivery']) && check_data() && check_qoh()) { $dn = &$_SESSION['Items']; if ($_POST['bo_policy']) { @@ -304,9 +280,11 @@ if (isset($_POST['process_delivery']) && check_data() && check_qoh()) { } $newdelivery = ($dn->trans_no == 0); - copy_to_cart(); - if ($newdelivery) new_doc_date($dn->document_date); + if ($newdelivery) + new_doc_date($dn->document_date); + $delivery_no = $dn->write($bo_policy); + if ($delivery_no == -1) { display_error(_("The entered reference is already in use.")); @@ -320,10 +298,10 @@ if (isset($_POST['process_delivery']) && check_data() && check_qoh()) { } else { meta_forward($_SERVER['PHP_SELF'], "UpdatedID=$delivery_no"); } - } + } } -if (isset($_POST['Update']) || isset($_POST['_Location_update']) || isset($_POST['qty'])) { +if (isset($_POST['Update']) || isset($_POST['_Location_update']) || isset($_POST['qty']) || isset($_POST['process_delivery'])) { $Ajax->activate('Items'); } //------------------------------------------------------------------------------ @@ -451,16 +429,22 @@ foreach ($_SESSION['Items']->line_items as $line=>$ln_itm) { // quantity input box. This allows for example a hook to modify the default quantity to what's dispatchable // (if there is not enough in hand), check at other location or other order people etc ... // This hook also returns a 'reason' (css classes) which can be used to theme the row. + // + // FIXME: hook_get_dispatchable definition does not allow qoh checks on transaction level + // (but anyway dispatch is checked again later before transaction is saved) + + $qty = $ln_itm->qty_dispatched; + if ($check = check_negative_stock($ln_itm->stock_id, -$ln_itm->qty_dispatched, $_POST['Location'], $_POST['DispatchDate'])) + $qty = $check['qty']; + + $q_class = hook_get_dispatchable_quantity($ln_itm, $_POST['Location'], $_POST['DispatchDate'], $qty); - $qoh = get_qoh_on_date($ln_itm->stock_id, $_POST['Location'], $_POST['DispatchDate']); - $q_class = hook_get_dispatchable_quantity($ln_itm, $_POST['Location'], $_POST['DispatchDate'], $qoh); // Skip line if needed if($q_class === 'skip') continue; if(is_array($q_class)) { list($ln_itm->qty_dispatched, $row_classes) = $q_class; $has_marked = true; } - } alt_table_row_color($k, $row_classes); @@ -520,7 +504,7 @@ label_row(_("Amount Total"), $display_total, "colspan=$colspan align=right","ali end_table(1); if ($has_marked) { - display_note(_("Marked items have insufficient quantities in stock as on day of delivery."), 0, 1, "class='red'"); + display_note(_("Marked items have insufficient quantities in stock as on day of delivery."), 0, 1, "class='stockmankofg'"); } start_table(TABLESTYLE2); diff --git a/sales/includes/cart_class.inc b/sales/includes/cart_class.inc index 64a237f3..e39ebf99 100644 --- a/sales/includes/cart_class.inc +++ b/sales/includes/cart_class.inc @@ -544,6 +544,40 @@ class cart return $total; } + + /* + Checks cart quantities on document_date. + Returns array of stock_ids which stock quantities would go negative on some day. + */ + function check_qoh($date=null, $location=null) + { + $low_stock = array(); + // check only for customer delivery and direct sales invoice + if (!($this->trans_type == ST_CUSTDELIVERY || ($this->trans_type == ST_SALESINVOICE && $this->trans_no==0))) + return $low_stock; + + // collect quantities by stock_id + $qtys = array(); + foreach ($this->line_items as $line_no => $line_item) + { + if (has_stock_holding($line_item->mb_flag)) + { + if (!$this->trans_no) // new delivery + $qtys[$line_item->stock_id]['qty'] = $line_item->quantity + @$qtys[$line_item->stock_id]['qty']; + else // DN modification: check change in quantity + $qtys[$line_item->stock_id]['qty'] = ($line_item->qty_dispatched-$line_item->quantity) + @$qtys[$line_item->stock_id]['qty']; + $qtys[$line_item->stock_id]['line'] = $line_no; + } + } + foreach($qtys as $stock_id => $sum) + { + if (check_negative_stock($stock_id, -$sum['qty'], $location ? $location : $this->Location, $date ? $date : $this->document_date)) + $low_stock[] = $stock_id; + } + + return $low_stock; + } + } /* end of class defintion */ class line_details diff --git a/sales/includes/ui/sales_order_ui.inc b/sales/includes/ui/sales_order_ui.inc index 2af45009..a07ceba0 100644 --- a/sales/includes/ui/sales_order_ui.inc +++ b/sales/includes/ui/sales_order_ui.inc @@ -127,7 +127,7 @@ function get_customer_details_to_order(&$order, $customer_id, $branch_id) $order->set_location($order->pos["pos_location"], $order->pos["location_name"]); } else $order->set_location($myrow["default_location"], $myrow["location_name"]); - + return $ret_error; } @@ -158,8 +158,8 @@ function display_order_summary($title, &$order, $editable_items=false) $k = 0; //row colour counter $id = find_submit('Edit'); - $has_marked = false; + $low_stock = $order->check_qoh($_POST['OrderDate'], $_POST['Location']); foreach ($order->get_items() as $line_no=>$stock_item) { @@ -169,23 +169,10 @@ function display_order_summary($title, &$order, $editable_items=false) $qoh_msg = ''; if (!$editable_items || $id != $line_no) { - if (!$SysPrefs->allow_negative_stock() && is_inventory_item($stock_item->stock_id) && - $order->trans_type != ST_SALESORDER && $order->trans_type!=ST_SALESQUOTE) { - $qoh = get_qoh_on_date($stock_item->stock_id, - $_POST['Location'], $_POST['delivery_date']); - if (($stock_item->qty_dispatched -$stock_item->qty_done) > $qoh) - { - // oops, we don't have enough of one of the component items - start_row("class='stockmankobg'"); - $qoh_msg .= $stock_item->stock_id . " - " . $stock_item->item_description . ": " . - _("Quantity On Hand") . " = " - . number_format2($qoh, get_qty_dec($stock_item->stock_id)) . '
'; - $has_marked = true; - } else - alt_table_row_color($k); - } else { + if (in_array($stock_item->stock_id, $low_stock)) + start_row("class='stockmankobg'"); // notice low stock status + else alt_table_row_color($k); - } view_stock_status_cell($stock_item->stock_id); @@ -244,12 +231,9 @@ function display_order_summary($title, &$order, $editable_items=false) end_row(); end_table(); - if ($has_marked) { + if ($low_stock) display_note(_("Marked items have insufficient quantities in stock as on day of delivery."), 0, 1, "class='stockmankofg'"); - if ($order->trans_type!=30 && !$SysPrefs->allow_negative_stock()) - display_error(_("The delivery cannot be processed because there is an insufficient quantity for item:") - . '
'. $qoh_msg); - } + div_end(); } diff --git a/sales/sales_order_entry.php b/sales/sales_order_entry.php index 90e76839..3873d4a5 100644 --- a/sales/sales_order_entry.php +++ b/sales/sales_order_entry.php @@ -333,7 +333,10 @@ function line_start_focus() { //-------------------------------------------------------------------------------- function can_process() { - global $Refs; + + global $Refs, $SysPrefs; + + copy_to_cart(); if (!get_post('customer_id')) { @@ -364,14 +367,20 @@ function can_process() { set_focus('AddItem'); return false; } - if ($_SESSION['Items']->payment_terms['cash_sale'] == 0) { - if (strlen($_POST['deliver_to']) <= 1) { - display_error(_("You must enter the person or company to whom delivery should be made to.")); - set_focus('deliver_to'); + + if (!$SysPrefs->allow_negative_stock() && ($low_stock = $_SESSION['Items']->check_qoh())) + { + display_error(_("This document cannot be processed because there is insufficient quantity for items marked.")); return false; } + if ($_SESSION['Items']->payment_terms['cash_sale'] == 0) { + if (strlen($_POST['deliver_to']) <= 1) { + display_error(_("You must enter the person or company to whom delivery should be made to.")); + set_focus('deliver_to'); + return false; + } if ($_SESSION['Items']->trans_type != ST_SALESQUOTE && strlen($_POST['delivery_address']) <= 1) { display_error( _("You should enter the street address in the box provided. Orders cannot be accepted without a valid street address.")); set_focus('delivery_address'); @@ -394,7 +403,6 @@ function can_process() { set_focus('delivery_date'); return false; } - //if (date1_greater_date2($_SESSION['Items']->document_date, $_POST['delivery_date'])) { if (date1_greater_date2($_POST['OrderDate'], $_POST['delivery_date'])) { if ($_SESSION['Items']->trans_type==ST_SALESQUOTE) display_error(_("The requested valid date is before the date of the quotation.")); @@ -435,7 +443,7 @@ if (isset($_POST['update'])) { } if (isset($_POST['ProcessOrder']) && can_process()) { - copy_to_cart(); + $modified = ($_SESSION['Items']->trans_no != 0); $so_type = $_SESSION['Items']->so_type; @@ -505,23 +513,8 @@ function check_item_data() set_focus('qty'); display_error(_("You attempting to make the quantity ordered a quantity less than has already been delivered. The quantity delivered cannot be modified retrospectively.")); return false; - } // Joe Hunt added 2008-09-22 ------------------------- - elseif ($is_inventory_item && $_SESSION['Items']->trans_type!=ST_SALESORDER && $_SESSION['Items']->trans_type!=ST_SALESQUOTE - && !$SysPrefs->allow_negative_stock()) - { - $qoh = get_qoh_on_date($_POST['stock_id'], $_POST['Location'], $_POST['OrderDate']); - $qoh_today = get_qoh_on_date($_POST['stock_id'], $_POST['Location'], null); // Of course we must also test for stock availability by todays date. - $qty = input_num('qty'); - if ($qty > $qoh || $qty > $qoh_today) - { - $stock = get_item($_POST['stock_id']); - display_error(_("The delivery cannot be processed because there is an insufficient quantity for item:") . - " " . $stock['stock_id'] . " - " . $stock['description'] . " - " . - _("Quantity On Hand") . " = " . number_format2($qoh, get_qty_dec($_POST['stock_id']))); - return false; - } - return true; } + $cost_home = get_standard_cost(get_post('stock_id')); // Added 2011-03-27 Joe Hunt $cost = $cost_home / get_exchange_rate_from_home_currency($_SESSION['Items']->customer_currency, $_SESSION['Items']->document_date); if (input_num('price') < $cost)