2
0
forked from Wavyzz/dolibarr

Merge pull request #16537 from altairisfr/pr_sn_unicity

Unique serial number management
This commit is contained in:
Laurent Destailleur
2021-03-06 11:22:44 +01:00
committed by GitHub
9 changed files with 86 additions and 50 deletions

View File

@@ -587,7 +587,7 @@ class modProduct extends DolibarrModules
)); ));
$this->import_regex_array[$r] = array_merge($this->import_regex_array[$r], array( $this->import_regex_array[$r] = array_merge($this->import_regex_array[$r], array(
'p.tobatch' => '^[0|1]$' 'p.tobatch' => '^[0|1|2]$'
)); ));
$this->import_convertvalue_array[$r] = array_merge($this->import_convertvalue_array[$r], array( $this->import_convertvalue_array[$r] = array_merge($this->import_convertvalue_array[$r], array(
@@ -679,7 +679,7 @@ class modProduct extends DolibarrModules
//clauses copied from import_fields_array //clauses copied from import_fields_array
if (!empty($conf->stock->enabled)) { if (!empty($conf->stock->enabled)) {
$import_sample = array_merge($import_sample, array( $import_sample = array_merge($import_sample, array(
'p.tobatch'=>"0 (don't use) / 1 (use batch/serial number)", 'p.tobatch'=>"0 (don't use) / 1 (use batch) / 2 (use serial number)",
'p.seuil_stock_alerte' => '', 'p.seuil_stock_alerte' => '',
'p.pmp' => '0', 'p.pmp' => '0',
'p.desiredstock' => '' 'p.desiredstock' => ''

View File

@@ -855,7 +855,7 @@ if ($id > 0 || !empty($ref)) {
// Already dispatched // Already dispatched
print '<td class="right">'.$products_dispatched[$objp->rowid].'</td>'; print '<td class="right">'.$products_dispatched[$objp->rowid].'</td>';
if (!empty($conf->productbatch->enabled) && $objp->tobatch == 1) { if (!empty($conf->productbatch->enabled) && $objp->tobatch > 0) {
$type = 'batch'; $type = 'batch';
print '<td class="right">'; print '<td class="right">';
print '</td>'; // Qty to dispatch print '</td>'; // Qty to dispatch
@@ -967,7 +967,7 @@ if ($id > 0 || !empty($ref)) {
print '</td>'; print '</td>';
print '<td>'; print '<td>';
if (!empty($conf->productbatch->enabled) && $objp->tobatch == 1) { if (!empty($conf->productbatch->enabled) && $objp->tobatch > 0) {
$type = 'batch'; $type = 'batch';
print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.$i.', \''.$type.'\')"'); print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.$i.', \''.$type.'\')"');
} else { } else {

View File

@@ -897,7 +897,7 @@ if ($ok && GETPOST('clean_product_stock_batch', 'alpha')) {
$sql = "SELECT p.rowid, p.ref, p.tobatch, ps.rowid as psrowid, ps.fk_entrepot, ps.reel, SUM(pb.qty) as reelbatch"; $sql = "SELECT p.rowid, p.ref, p.tobatch, ps.rowid as psrowid, ps.fk_entrepot, ps.reel, SUM(pb.qty) as reelbatch";
$sql .= " FROM ".MAIN_DB_PREFIX."product as p, ".MAIN_DB_PREFIX."product_stock as ps LEFT JOIN ".MAIN_DB_PREFIX."product_batch as pb ON ps.rowid = pb.fk_product_stock"; $sql .= " FROM ".MAIN_DB_PREFIX."product as p, ".MAIN_DB_PREFIX."product_stock as ps LEFT JOIN ".MAIN_DB_PREFIX."product_batch as pb ON ps.rowid = pb.fk_product_stock";
$sql .= " WHERE p.rowid = ps.fk_product"; $sql .= " WHERE p.rowid = ps.fk_product";
$sql .= " AND p.tobatch = 1"; $sql .= " AND p.tobatch > 0";
$sql .= " GROUP BY p.rowid, p.ref, p.tobatch, ps.rowid, ps.fk_entrepot, ps.reel"; $sql .= " GROUP BY p.rowid, p.ref, p.tobatch, ps.rowid, ps.fk_entrepot, ps.reel";
$sql .= " HAVING reel != SUM(pb.qty) or SUM(pb.qty) IS NULL"; $sql .= " HAVING reel != SUM(pb.qty) or SUM(pb.qty) IS NULL";
print $sql; print $sql;
@@ -981,7 +981,7 @@ if ($ok && GETPOST('clean_product_stock_negative_if_batch', 'alpha')) {
$sql = "SELECT p.rowid, p.ref, p.tobatch, ps.rowid as psrowid, ps.fk_entrepot, ps.reel, SUM(pb.qty) as reelbatch"; $sql = "SELECT p.rowid, p.ref, p.tobatch, ps.rowid as psrowid, ps.fk_entrepot, ps.reel, SUM(pb.qty) as reelbatch";
$sql .= " FROM ".MAIN_DB_PREFIX."product as p, ".MAIN_DB_PREFIX."product_stock as ps, ".MAIN_DB_PREFIX."product_batch as pb"; $sql .= " FROM ".MAIN_DB_PREFIX."product as p, ".MAIN_DB_PREFIX."product_stock as ps, ".MAIN_DB_PREFIX."product_batch as pb";
$sql .= " WHERE p.rowid = ps.fk_product AND ps.rowid = pb.fk_product_stock"; $sql .= " WHERE p.rowid = ps.fk_product AND ps.rowid = pb.fk_product_stock";
$sql .= " AND p.tobatch = 1"; $sql .= " AND p.tobatch > 0";
$sql .= " GROUP BY p.rowid, p.ref, p.tobatch, ps.rowid, ps.fk_entrepot, ps.reel"; $sql .= " GROUP BY p.rowid, p.ref, p.tobatch, ps.rowid, ps.fk_entrepot, ps.reel";
$sql .= " HAVING reel != SUM(pb.qty)"; $sql .= " HAVING reel != SUM(pb.qty)";
$resql = $db->query($sql); $resql = $db->query($sql);

View File

@@ -24,3 +24,5 @@ ProductLotSetup=Setup of module lot/serial
ShowCurrentStockOfLot=Show current stock for couple product/lot ShowCurrentStockOfLot=Show current stock for couple product/lot
ShowLogOfMovementIfLot=Show log of movements for couple product/lot ShowLogOfMovementIfLot=Show log of movements for couple product/lot
StockDetailPerBatch=Stock detail per lot StockDetailPerBatch=Stock detail per lot
SerialNumberAlreadyInUse=Serial number %s is already used for product %s
TooManyQtyForSerialNumber=You can only have one product %s for serial number %S

View File

@@ -24,3 +24,5 @@ ProductLotSetup=Configuration du module lot/série
ShowCurrentStockOfLot=Afficher le stock actuel pour le couple produit / lot ShowCurrentStockOfLot=Afficher le stock actuel pour le couple produit / lot
ShowLogOfMovementIfLot=Afficher l'historique des mouvements de couple produit / lot ShowLogOfMovementIfLot=Afficher l'historique des mouvements de couple produit / lot
StockDetailPerBatch=Stock détaillé par lot StockDetailPerBatch=Stock détaillé par lot
SerialNumberAlreadyInUse=Le numéro de série %s est déjà utilisé pour le produit %s
TooManyQtyForSerialNumber=Vous ne pouvez avoir qu'un produit %s avec le numéro de série %s

View File

@@ -1084,11 +1084,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
// Batch number management // Batch number management
if (!empty($conf->productbatch->enabled)) { if (!empty($conf->productbatch->enabled)) {
print '<tr><td>'.$langs->trans("ManageLotSerial").'</td><td colspan="3">'; print '<tr><td>'.$langs->trans("ManageLotSerial").'</td><td colspan="3">';
if (empty($conf->global ->MAIN_ADVANCE_NUMLOT)) { $statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"), '2' => $langs->trans("ProductStatusOnSerial"));
$statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"));
} else {
$statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"), '2' => $langs->trans("ProductStatusOnSerial"));
}
print $form->selectarray('status_batch', $statutarray, GETPOST('status_batch')); print $form->selectarray('status_batch', $statutarray, GETPOST('status_batch'));
print '</td></tr>'; print '</td></tr>';
} }
@@ -1548,11 +1544,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
if ($conf->productbatch->enabled) { if ($conf->productbatch->enabled) {
if ($object->isProduct() || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) { if ($object->isProduct() || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
print '<tr><td>'.$langs->trans("ManageLotSerial").'</td><td colspan="3">'; print '<tr><td>'.$langs->trans("ManageLotSerial").'</td><td colspan="3">';
if (empty($conf->global ->MAIN_ADVANCE_NUMLOT)) { $statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"), '2' => $langs->trans("ProductStatusOnSerial"));
$statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"));
} else {
$statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"), '2' => $langs->trans("ProductStatusOnSerial"));
}
print $form->selectarray('status_batch', $statutarray, $object->status_batch); print $form->selectarray('status_batch', $statutarray, $object->status_batch);
print '</td></tr>'; print '</td></tr>';
} }
@@ -2040,11 +2032,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
if (!empty($conf->productbatch->enabled)) { if (!empty($conf->productbatch->enabled)) {
if ($object->isProduct() || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) { if ($object->isProduct() || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) {
print '<tr><td>'.$langs->trans("ManageLotSerial").'</td><td colspan="2">'; print '<tr><td>'.$langs->trans("ManageLotSerial").'</td><td colspan="2">';
if (!empty($conf->use_javascript_ajax) && $usercancreate && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE) && empty($conf->global->MAIN_ADVANCE_NUMLOT)) { print $object->getLibStatut(0, 2);
print ajax_object_onoff($object, 'status_batch', 'tobatch', 'ProductStatusOnBatch', 'ProductStatusNotOnBatch');
} else {
print $object->getLibStatut(0, 2);
}
print '</td></tr>'; print '</td></tr>';
} }
} }

View File

@@ -4747,10 +4747,10 @@ class Product extends CommonObject
if ($type == 2) { if ($type == 2) {
switch ($mode) { switch ($mode) {
case 0: case 0:
$label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatch') : ($status == 1 || empty($conf->global->MAIN_ADVANCE_NUMLOT) ? $langs->trans('ProductStatusOnBatch') : $langs->trans('ProductStatusOnSerial'))); $label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatch') : ($status == 1 ? $langs->trans('ProductStatusOnBatch') : $langs->trans('ProductStatusOnSerial')));
return dolGetStatus($label); return dolGetStatus($label);
case 1: case 1:
$label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatchShort') : ($status == 1 || empty($conf->global->MAIN_ADVANCE_NUMLOT) ? $langs->trans('ProductStatusOnBatchShort') : $langs->trans('ProductStatusOnSerialShort'))); $label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->trans('ProductStatusOnBatchShort') : $langs->trans('ProductStatusOnSerialShort')));
return dolGetStatus($label); return dolGetStatus($label);
case 2: case 2:
return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2); return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2);
@@ -4788,10 +4788,10 @@ class Product extends CommonObject
$labelStatus = $langs->trans('ProductStatusOnBuyShort'); $labelStatus = $langs->trans('ProductStatusOnBuyShort');
$labelStatusShort = $langs->trans('ProductStatusOnBuy'); $labelStatusShort = $langs->trans('ProductStatusOnBuy');
} elseif ($type == 2) { } elseif ($type == 2) {
$labelStatus = ($status == 1 || empty($conf->global->MAIN_ADVANCE_NUMLOT) ? $langs->trans('ProductStatusOnBatch') : $langs->trans('ProductStatusOnSerial')); $labelStatus = ($status == 1 ? $langs->trans('ProductStatusOnBatch') : $langs->trans('ProductStatusOnSerial'));
$labelStatusShort = ($status == 1 || empty($conf->global->MAIN_ADVANCE_NUMLOT) ? $langs->trans('ProductStatusOnBatchShort') : $langs->trans('ProductStatusOnSerialShort')); $labelStatusShort = ($status == 1 ? $langs->trans('ProductStatusOnBatchShort') : $langs->trans('ProductStatusOnSerialShort'));
} }
} elseif (! empty($conf->global->MAIN_ADVANCE_NUMLOT) && $type == 2 && $status == 2) { } elseif ( $type == 2 && $status == 2 ) {
$labelStatus = $langs->trans('ProductStatusOnSerial'); $labelStatus = $langs->trans('ProductStatusOnSerial');
$labelStatusShort = $langs->trans('ProductStatusOnSerialShort'); $labelStatusShort = $langs->trans('ProductStatusOnSerialShort');
} }

View File

@@ -971,18 +971,13 @@ if ($resql) {
// To batch // To batch
if (!empty($arrayfields['p.tobatch']['checked'])) { if (!empty($arrayfields['p.tobatch']['checked'])) {
print '<td class="liste_titre center">'; print '<td class="liste_titre center">';
$statutarray = array(
if (empty($conf->global ->MAIN_ADVANCE_NUMLOT)) { '-1' => '',
print $form->selectyesno('search_tobatch', $search_tobatch, 1, false, 1); '0' => $langs->trans("ProductStatusNotOnBatchShort"),
} else { '1' => $langs->trans("ProductStatusOnBatchShort"),
$statutarray = array( '2' => $langs->trans("ProductStatusOnSerialShort")
'-1' => '', );
'0' => $langs->trans("ProductStatusNotOnBatchShort"), print $form->selectarray('search_tobatch', $statutarray, $search_tobatch);
'1' => $langs->trans("ProductStatusOnBatchShort"),
'2' => $langs->trans("ProductStatusOnSerialShort")
);
print $form->selectarray('search_tobatch', $statutarray, $search_tobatch);
}
print '</td>'; print '</td>';
} }
// Country // Country
@@ -1672,11 +1667,7 @@ if ($resql) {
// Lot/Serial // Lot/Serial
if (!empty($arrayfields['p.tobatch']['checked'])) { if (!empty($arrayfields['p.tobatch']['checked'])) {
print '<td class="center">'; print '<td class="center">';
if (empty($conf->global->MAIN_ADVANCE_NUMLOT)) { print $product_static->getLibStatut(1, 2);
print yn($obj->tobatch);
} else {
print $product_static->getLibStatut(1, 2);
}
print '</td>'; print '</td>';
if (!$i) { if (!$i) {
$totalarray['nbfield']++; $totalarray['nbfield']++;

View File

@@ -192,7 +192,7 @@ class MouvementStock extends CommonObject
} }
} }
// end hook at beginning // end hook at beginning
// Clean parameters // Clean parameters
$price = price2num($price, 'MU'); // Clean value for the casse we receive a float zero value, to have it a real zero value. $price = price2num($price, 'MU'); // Clean value for the casse we receive a float zero value, to have it a real zero value.
if (empty($price)) $price = 0; if (empty($price)) $price = 0;
@@ -568,14 +568,32 @@ class MouvementStock extends CommonObject
// Update detail stock for batch product // Update detail stock for batch product
if (!$error && !empty($conf->productbatch->enabled) && $product->hasbatch() && !$skip_batch) if (!$error && !empty($conf->productbatch->enabled) && $product->hasbatch() && !$skip_batch)
{ {
if ($id_product_batch > 0) // check unicity for serial numbered equipments ( different for lots managed products)
if ( $product->status_batch == 2 && $qty > 0 )
{ {
$result = $this->createBatch($id_product_batch, $qty); if ( $this->getBatchCount($fk_product, $batch) > 0 )
} else { {
$param_batch = array('fk_product_stock' =>$fk_product_stock, 'batchnumber'=>$batch); $error++;
$result = $this->createBatch($param_batch, $qty); $this->errors[] = $langs->trans("SerialNumberAlreadyInUse", $batch, $product->ref);
}
elseif ( $qty > 1 )
{
$error++;
$this->errors[] = $langs->trans("TooManyQtyForSerialNumber", $product->ref, $batch);
}
}
if ( ! $error )
{
if ($id_product_batch > 0)
{
$result = $this->createBatch($id_product_batch, $qty);
} else {
$param_batch = array('fk_product_stock' =>$fk_product_stock, 'batchnumber'=>$batch);
$result = $this->createBatch($param_batch, $qty);
}
if ($result < 0) $error++;
} }
if ($result < 0) $error++;
} }
// Update PMP and denormalized value of stock qty at product level // Update PMP and denormalized value of stock qty at product level
@@ -1208,4 +1226,39 @@ class MouvementStock extends CommonObject
return $this->deleteCommon($user, $notrigger); return $this->deleteCommon($user, $notrigger);
//return $this->deleteCommon($user, $notrigger, 1); //return $this->deleteCommon($user, $notrigger, 1);
} }
/**
* Retrieve number of equipments for a product batch
*
* @param int $fk_product Product id
* @param varchar $batch batch number
* @return int <0 if KO, number of equipments if OK
*/
private function getBatchCount($fk_product, $batch)
{
global $conf;
$cpt = 0;
$sql = "SELECT sum(pb.qty) as cpt";
$sql .= " FROM ".MAIN_DB_PREFIX."product_batch as pb";
$sql .= " INNER JOIN ".MAIN_DB_PREFIX."product_stock as ps ON ps.rowid = pb.fk_product_stock";
$sql .= " WHERE ps.fk_product = " . $fk_product;
$sql .= " AND pb.batch = '" . $this->db->escape($batch) . "'";
$result = $this->db->query($sql);
if ($result) {
if ($this->db->num_rows($result)) {
$obj = $this->db->fetch_object($result);
$cpt = $obj->cpt;
}
$this->db->free($result);
} else {
dol_print_error($this->db);
return -1;
}
return $cpt;
}
} }