noalyss-commit
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Noalyss-commit] [noalyss] 25/26: Improve code : Card_Property is separa


From: Dany De Bontridder
Subject: [Noalyss-commit] [noalyss] 25/26: Improve code : Card_Property is separated from Card (Fiche) , add more phpunit tests
Date: Wed, 11 Aug 2021 11:08:18 -0400 (EDT)

sparkyx pushed a commit to branch master
in repository noalyss.

commit 4b913f1e88c9a012163631967e93815b4aa54e4b
Author: sparkyx <danydb@noalyss.eu>
AuthorDate: Wed Aug 11 13:06:01 2021 +0200

    Improve code : Card_Property is separated from Card (Fiche) , add more
    phpunit tests
---
 include/class/card_property.class.php              | 618 ++++++++++++++
 include/class/fiche.class.php                      | 901 +++------------------
 include/class/fiche_attr.class.php                 |   9 +-
 include/class/fiche_def.class.php                  |   8 +-
 include/constant.php                               |   3 +-
 unit-test/global.example.php                       |  18 +-
 unit-test/global.php                               |   1 +
 .../include/class/acc_ledger_purchase.Test.php     |   4 +-
 unit-test/include/class/acc_ledger_sale.Test.php   |   5 +-
 unit-test/include/class/card_property.Test.php     | 186 +++++
 unit-test/include/class/databaseTest.class.php     |  41 +
 unit-test/include/class/document.Test.php          |  29 +-
 unit-test/include/class/fiche.Test.php             | 243 ++++--
 ...le_sql_sqlTest.class.php => table_sql.Test.php} |   0
 unit-test/test-file.sh                             |  31 +-
 15 files changed, 1244 insertions(+), 853 deletions(-)

diff --git a/include/class/card_property.class.php 
b/include/class/card_property.class.php
new file mode 100644
index 0000000..f99b081
--- /dev/null
+++ b/include/class/card_property.class.php
@@ -0,0 +1,618 @@
+<?php
+
+/*
+ *   This file is part of NOALYSS.
+ *
+ *   PhpCompta is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   PhpCompta is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with PhpCompta; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+// Copyright (2002-2021) Author Dany De Bontridder <danydb@noalyss.eu>
+
+/**
+ * @file
+ * @brief manage the attribute of a card 
+ */
+
+/**
+ * @class Card_Property
+ * @brief contains the attributes of a card , manage them, save them , ...
+ * 
+ */
+class Card_Property
+{
+
+    //!< ad_id int id of the attribute attr_def.ad_id
+    var $ad_id;
+    //!< ad_text string label of the attribute attr_def.ad_def
+    var $ad_text;
+    //!< av_text string value of this attribute 
+    var $av_text;
+    //!< ad_type string : type of this attribute (select, text ,...)
+    var $ad_type;
+    //!< ad_size int size of the attribute
+    var $ad_size;
+    //!< ad_extra extra info for a attribute
+    var $ad_extra;
+    //!< jnt_order order to display
+    var $jnt_order;
+    //!< cn database connexion
+    var $cn;
+
+    function __construct($cn, $ad_id=0)
+    {
+        $this->cn=$cn;
+        $this->ad_id=0;
+    }
+
+    public function get_ad_id()
+    {
+        return $this->ad_id;
+    }
+
+    public function get_ad_text()
+    {
+        return $this->ad_text;
+    }
+
+    public function get_av_text()
+    {
+        return $this->av_text;
+    }
+
+    public function get_ad_type()
+    {
+        return $this->ad_type;
+    }
+
+    public function get_ad_size()
+    {
+        return $this->ad_size;
+    }
+
+    public function get_ad_extra()
+    {
+        return $this->ad_extra;
+    }
+
+    public function get_jnt_order()
+    {
+        return $this->jnt_order;
+    }
+
+    public function set_ad_id($ad_id): void
+    {
+        $this->ad_id=$ad_id;
+    }
+
+    public function set_ad_text($ad_text): void
+    {
+        $this->ad_text=$ad_text;
+    }
+
+    public function set_av_text($av_text): void
+    {
+        $this->av_text=$av_text;
+    }
+
+    public function set_ad_type($ad_type): void
+    {
+        $this->ad_type=$ad_type;
+    }
+
+    public function set_ad_size($ad_size): void
+    {
+        $this->ad_size=$ad_size;
+    }
+
+    public function set_ad_extra($ad_extra): void
+    {
+        $this->ad_extra=$ad_extra;
+    }
+
+    public function set_jnt_order($jnt_order): void
+    {
+        $this->jnt_order=$jnt_order;
+    }
+
+    /**
+     * @brief Load all the attribute of a card , it modifies the parameter 
$fiche. Usually called from fiche::insert 
+     * and fiche::update . In the same time, it will synchronize the 
attributes which the database. 
+     * The attributes (public.fiche_detail) will be ordered in the member 
attribute $fiche->attribut
+     * @param $fiche Fiche Full fill this card with all the attribute
+     * @see  Fiche::update Fiche::insert
+     * @exception EXC_PARAM_VALUE it is not possible to compute the default 
attributes for a new card without the card
+     * category
+     * 
+     */
+    static function load(Fiche $fiche)
+    {
+        // if card is not yet saved then we don't load it from database but 
all the properties are set to empty
+        if ($fiche->id==0 && $fiche->fiche_def !=0 )
+        {
+            $fiche_def=new Fiche_Def($this->cn,$fiche->fiche_def);
+            $aProperty=$fiche_def->getAttribut();
+            $fiche->attribut=$aProperty;
+            return;
+        } elseif ($fiche->id==0 && $fiche->fiche_def ==0 )
+        {
+            throw new Exception("CP147. Card category cannot be empty 
(fiche->set_fiche_def)",EXC_PARAM_VALUE);
+        }
+        $sql="select *
+             from
+                   fiche
+             natural join fiche_detail
+            join jnt_fic_attr on (jnt_fic_attr.fd_id=fiche.fd_id and 
fiche_detail.ad_id=jnt_fic_attr.ad_id)
+             join attr_def on (attr_def.ad_id=fiche_detail.ad_id) where f_id= 
$1".
+                " order by jnt_order";
+
+        $Ret=$fiche->cn->exec_sql($sql, [$fiche->id]);
+        if (($Max=Database::num_row($Ret))==0)
+            return;
+        for ($i=0; $i<$Max; $i++)
+        {
+            $row=Database::fetch_array($Ret, $i);
+            $fiche->fiche_def=$row['fd_id'];
+            $fiche->set_f_enable($row['f_enable']);
+            $t=new Card_Property($fiche->cn);
+            $t->ad_id=$row['ad_id'];
+            $t->ad_text=$row['ad_text'];
+            $t->av_text=$row['ad_value'];
+            $t->ad_type=$row['ad_type'];
+            $t->ad_size=$row['ad_size'];
+            $t->ad_extra=$row['ad_extra'];
+            $t->jnt_order=$row['jnt_order'];
+            $fiche->attribut[$i]=$t;
+        }
+        $e=new Fiche_Def($fiche->cn, $fiche->fiche_def);
+        $e->GetAttribut();
+
+        if (sizeof($fiche->attribut)!=sizeof($e->attribut))
+        {
+
+            /*
+             * !! Missing attribute
+             */
+            foreach ($e->attribut as $f)
+            {
+                $flag=0;
+                foreach ($fiche->attribut as $g)
+                {
+                    if ($g->ad_id==$f->ad_id)
+                        $flag=1;
+                }
+                if ($flag==0)
+                {
+                    // there's a missing one, we insert it
+                    $t=new Card_Property($fiche->cn, $f->ad_id);
+                    $t->av_text="";
+                    $t->ad_text=$f->ad_text;
+                    $t->jnt_order=$f->jnt_order;
+                    $t->ad_type=$f->ad_type;
+                    $t->ad_size=$f->ad_size;
+                    $t->ad_id=$f->ad_id;
+                    $t->ad_extra=$f->ad_extra;
+                    $fiche->attribut[$Max]=$t;
+                    $Max++;
+                } // if flag == 0
+            }// foreach
+        }//missing attribut
+    }
+
+    /**
+     * @brief input a property of a card
+     * @param Fiche_Def $p_fiche_def
+     * @return string HTML string with the right input type
+     */
+    function input($p_fiche_def=null)
+    {
+        $bulle=""; $msg="";
+        $r="";
+        if ($this->ad_id==ATTR_DEF_ACCOUNT)
+        {
+            $w=new IPoste("av_text".$this->ad_id);
+            $w->set_attribute('ipopup', 'ipop_account');
+            $w->set_attribute('jrn', '0');
+            $w->set_attribute('account', "av_text".$this->ad_id);
+            $w->dbl_click_history();
+            $w->value=$this->av_text;
+            //  account created automatically
+            $sql="select account_auto($p_fiche_def->id)";
+            $ret_sql=$this->cn->exec_sql($sql);
+            $a=Database::fetch_array($ret_sql, 0);
+            $label=new ISpan();
+            $label->name="av_text".$this->ad_id."_label";
+
+            if ($a['account_auto']=='t')
+            {
+                $msg.=$label->input()." <span style=\"color:red\">".
+                        _("Rappel: Poste créé automatiquement à partir de ")
+                        .$p_fiche_def->class_base." </span> ";
+            }
+            else
+            {
+                // if there is a class base in fiche_def_ref, this account 
will be the
+                // the default one
+                if (strlen(trim($p_fiche_def->class_base))!=0)
+                {
+                    $msg.="<TD>".$label->input()." <span 
style=\"color:red\">"._("Rappel: Poste par défaut sera ").
+                            $p_fiche_def->class_base.
+                            " !</span> ";
+                    $w->value=$p_fiche_def->class_base;
+                }
+            }
+            $r.="<TR>".td(_("Poste Comptable"), ' class="highlight input_text" 
').td($w->input().$msg)."</TR>";
+            return $r;
+        }
+        elseif ($this->ad_id==ATTR_DEF_TVA)
+        {
+            $w=new ITva_Popup('popup_tva');
+            $w->table=1;
+            $w->value=$this->av_text;
+        }
+        else
+        {
+            switch ($this->ad_type)
+            {
+                case 'text':
+                    $w=new IText();
+                    $w->css_size="100%";
+                    $w->value=$this->av_text;
+                    break;
+                case 'numeric':
+                    $w=new INum();
+                    $w->prec=($this->ad_extra=="")?2:$this->ad_extra;
+                    $w->size=$this->ad_size;
+                    $w->value=$this->av_text;
+                    break;
+                case 'date':
+                    $w=new IDate();
+                    $w->value=$this->av_text;
+                    break;
+                case 'zone':
+                    $w=new ITextArea();
+                    $w->style=' class="itextarea" 
style="margin:0px;width:100%"';
+                    $w->value=$this->av_text;
+                    break;
+                case 'poste':
+                    $w=new IPoste("av_text".$this->ad_id);
+                    $w->set_attribute('ipopup', 'ipop_account');
+                    $w->set_attribute('account', "av_text".$this->ad_id);
+                    $w->table=1;
+                    $bulle=Icon_Action::infobulle(14);
+                    $w->value=$this->av_text;
+                    break;
+                case 'check':
+                    $w=new InputSwitch("av_text".$this->ad_id);
+                    $w->value=(trim($w->value)=="")?1:$w->value;
+                    break;
+                case 'select':
+                    $w=new ISelect("av_text".$this->ad_id);
+                    $w->value=$this->cn->make_array($this->ad_extra);
+                    $w->style='style="width:100%"';
+                    $w->value=$this->av_text;
+                    break;
+                case 'card':
+                    $w=new ICard("av_text".$this->ad_id);
+                    // filter on frd_id
+                    $w->extra=$this->ad_extra;
+                    $w->extra2=0;
+                    $w->id=uniqid();
+                    $label=new ISpan();
+                    $filter=$this->ad_extra;
+                    $w->width=$this->ad_size;
+                    $w->extra=$filter;
+                    $w->extra2=0;
+                    $w->limit=6;
+                    $label->name="av_text".$this->ad_id.$w->id."_label";
+                    $w->set_attribute('ipopup', 'ipopcard');
+                    $w->set_attribute('typecard', $this->ad_extra);
+                    $w->set_attribute('inp', $w->id);
+                    $w->set_attribute('label', 
"av_text".$this->ad_id.$w->id."_label");
+                    $w->autocomplete=1;
+                    $w->dblclick="fill_ipopcard(this);";
+                    $msg=$w->search();
+                    $msg.=$label->input();
+                    $w->value=$this->av_text;
+                    break;
+            }
+            $w->table=0;
+        }
+
+        $w->label=$this->ad_text;
+        $w->name="av_text".$this->ad_id;
+        if 
($this->ad_id==21||$this->ad_id==22||$this->ad_id==20||$this->ad_id==31)
+        {
+            $bulle=Icon_Action::infobulle(21);
+        }
+
+        // Warning length quickcode
+        if ($this->ad_id==ATTR_DEF_QUICKCODE)
+        {
+            $bulle=Icon_Action::warnbulle(76);
+        }
+        if ($this->ad_id==ATTR_DEF_NAME||$this->ad_id==ATTR_DEF_QUICKCODE)
+        {
+            $class=" input_text highlight info";
+        }
+        else
+        {
+            $class="input_text";
+        }
+        $r.="<TR>".td(_($w->label)." $bulle", ' class="'.$class.'" 
').td($w->input()." $msg")." </TR>";
+        return $r;
+    }
+
+    /**
+     * @brief Compute a HTML string in a TR element with information of this 
card property
+     * 
+     * @return string HTML into tr
+     */
+    function print()
+    {
+        $w=new IText();
+        $w->table=1;
+        $w->readOnly=true;
+        $w->css_size="100%";
+        $msg="";
+        $bulle="";
+        $ret="";
+        $value=$this->av_text;
+        if 
($this->ad_id==21||$this->ad_id==22||$this->ad_id==20||$this->ad_id==31)
+        {
+            $bulle=Icon_Action::infobulle(21);
+        }
+
+        // Warning length quickcode
+        if ($this->ad_id==ATTR_DEF_QUICKCODE)
+        {
+            $bulle=Icon_Action::warnbulle(76);
+        }
+        if ($this->ad_id==ATTR_DEF_NAME||$this->ad_id==ATTR_DEF_QUICKCODE)
+        {
+            $class=" input_text highlight info";
+        }
+        else
+        {
+            $class="input_text";
+        }
+        switch ($this->ad_type)
+        {
+            case 'select':
+                $x=new ISelect();
+                $x->value=$this->cn->make_array($this->ad_extra);
+                $x->selected=$this->av_text;
+                $value=$x->display();
+                $w->value=$value;
+                break;
+            case 'check':
+                $w=new InputSwitch("av_text".$this->ad_id);
+                $w->value=$this->av_text;
+                $w->value=(trim($w->value)=="")?1:$w->value;
+                break;
+            default:
+                $w->value=$this->av_text;
+        }
+        $ret.="<TR>".td(_($this->ad_text)." $bulle", ' class="'.$class.'" 
').td($value." $msg",
+                        'style="border:1px solid blue"')." </TR>";
+        return $ret;
+    }
+
+    /* !
+     * \brief  update all the data of the card , including f_enable. if we are 
in a transaction
+     * we don't commit here , else if not then a transaction is started and 
committed . The member attributes 
+     * $p_fiche->attribut will be saved into fiche_detail after transforming 
if needed. 
+     * If a transaction is started if there is none, so updating a card is 
always in a transaction. 
+     *
+     * 
+     */
+
+    static function update(Fiche $p_fiche)
+    {
+        //transaction in the function or from the caller 
+        $commit=false;
+        try
+        {
+            // are we inside a transaction (between BEGIN - COMMIT )
+            if ($p_fiche->cn->status()==PGSQL_TRANSACTION_IDLE)
+            {
+                $p_fiche->cn->start();
+                $commit=true;
+            }
+
+            $p_fiche->cn->exec_sql("update fiche set f_enable=$1 where 
f_id=$2",
+                    array($p_fiche->get_f_enable(), $p_fiche->id));
+
+            $name = $p_fiche->strAttribut(ATTR_DEF_NAME);
+            // parse the attribute
+            foreach ($p_fiche->attribut as $value)
+            {
+                // retrieve jft_id to update table attr_value
+                $sql=" select jft_id from fiche_detail where ad_id=$1 and 
f_id=$2";
+                $Ret=$p_fiche->cn->exec_sql($sql, [$value->ad_id, 
$p_fiche->id]);
+
+                // if attribute doesn't exist, then we insert one, 
+                if (Database::num_row($Ret)==0)
+                {
+                    // we need to insert this new attribut , $jft_id contains 
the PK of fiche_detail
+                    $jft_id=$p_fiche->cn->get_next_seq('s_jnt_fic_att_value');
+
+                    $sql2="insert into 
fiche_detail(jft_id,ad_id,f_id,ad_value) values ($1,$2,$3,NULL)";
+
+                    $ret2=$p_fiche->cn->exec_sql($sql2, array($jft_id, 
$value->ad_id, $p_fiche->id));
+                }
+                else
+                {
+                    $tmp=Database::fetch_array($Ret, 0);
+                    // $jft_id contains the PK of fiche_detail
+                    $jft_id=$tmp['jft_id'];
+                }
+                
+                // Special traitement
+                // quickcode
+                if ($value->ad_id==ATTR_DEF_QUICKCODE)
+                {
+                    $sql=sprintf("select update_quick_code(%d,'%s')", $jft_id, 
sql_string($value->av_text));
+                    $p_fiche->cn->exec_sql($sql);
+                    continue;
+                }
+                // name
+                if ($value->ad_id==ATTR_DEF_NAME && 
strlen(trim($value->av_text))==0 )
+                {
+                        continue;
+                }
+                // account
+                if ($value->ad_id==ATTR_DEF_ACCOUNT)
+                {
+                    $v=mb_strtoupper($value->av_text);
+                    // 2 accounts given 
+                    if (trim($v)!='')
+                    {
+                        if (strpos($v, ',')!=0)
+                        {
+                            $ac_array=explode(",", $v);
+                            if (count($ac_array)<>2)
+                                throw new Exception('Désolé, il y a trop de 
virgule dans le poste comptable '.h($v));
+                            $part1=$ac_array[0];
+                            $part2=$ac_array[1];
+                            $part1=$p_fiche->cn->get_value('select 
format_account($1)', array($part1));
+                            $part2=$p_fiche->cn->get_value('select 
format_account($1)', array($part2));
+
+                            if (mb_strlen($part1)>40)
+                                throw new Exception("CP475."._("Poste 
comptable trop long"), 1);
+                            if (mb_strlen($part2)>40)
+                                throw new Exception("CP476."._("Poste 
comptable trop long"), 1);
+
+                            $part1=$p_fiche->cn->get_value('select 
format_account($1)', array($part1));
+                            $acc_account1=new Acc_Account($p_fiche->cn, 
$part1);
+
+                            if ($acc_account1->get_parameter("id")==-1)
+                            {
+                                $account_name=$name;
+                                $acc_account1->set_parameter("pcm_lib", 
$account_name);
+                                $acc_account1->set_parameter('pcm_direct_use', 
"Y");
+                                $parent=$acc_account1->find_parent();
+                                $acc_account1->set_parameter("pcm_val_parent", 
$parent);
+                                $acc_account1->save();
+                            }
+                            // Check that the accounting can be used directly
+                            if 
($acc_account1->get_parameter('pcm_direct_use')=='N')
+                            {
+                                throw new Exception("CP493."._("Utilisation 
directe interdite du poste comptable $part1"));
+                            }
+                            // Part 2
+                            $part2=$p_fiche->cn->get_value('select 
format_account($1)', array($part2));
+                            $acc_account2=new Acc_Account($p_fiche->cn, 
$part2);
+
+                            if ($acc_account2->get_parameter("id")==-1)
+                            {
+                                $account_name=$name;
+                                $acc_account2->set_parameter("pcm_lib", 
$account_name);
+                                $acc_account2->set_parameter('pcm_direct_use', 
"Y");
+                                $parent=$acc_account2->find_parent();
+                                $acc_account2->set_parameter("pcm_val_parent", 
$parent);
+                                $acc_account2->save();
+                            }
+
+                            // Check that the accounting can be used directly
+                            if 
($acc_account2->get_parameter('pcm_direct_use')=='N')
+                            {
+                                throw new Exception("CP511."._("Utilisation 
directe interdite du poste comptable $part2"));
+                            }
+                            $v=$part1.','.$part2;
+                        }
+                        else
+                        {
+                            if (mb_strlen($v)>40)
+                                throw new Exception("CP520."._("Poste 
comptable trop long"), 1);
+                            $acc_account=new Acc_Account($p_fiche->cn, $v);
+                            // Set default for new accounting
+                            if ($acc_account->get_parameter("id")==-1)
+                            {
+                                $account_name=$name;
+                                $acc_account->set_parameter("pcm_lib", 
$account_name);
+                                // By Default can be used directly
+                                $acc_account->set_parameter('pcm_direct_use', 
"Y");
+                                $parent=$acc_account->find_parent();
+                                $acc_account->set_parameter("pcm_val_parent", 
$parent);
+                                $acc_account->save();
+                            }
+
+                            $acc_account=new Acc_Account($p_fiche->cn, $v);
+                            if 
($acc_account->get_parameter('pcm_direct_use')=='N')
+                            {
+                                throw new Exception("CP537."._("Utilisation 
directe interdite du poste comptable $v"));
+                            }
+                        }
+                        $sql=sprintf("select account_update(%d,'%s')", 
$p_fiche->id, $v);
+                        try
+                        {
+                            $p_fiche->cn->exec_sql($sql);
+                        }
+                        catch (Exception $e)
+                        {
+                            throw new Exception("CP546."._("opération 
annulée")." ".$e->getMessage());
+                        }
+                        continue;
+                    }
+                    if (strlen(trim($v))==0)
+                    {
+
+                        $sql=sprintf("select account_update(%d,null)", 
$p_fiche->id);
+                        try
+                        {
+                            $Ret=$p_fiche->cn->exec_sql($sql);
+                        }
+                        catch (Exception $e)
+                        {
+                            throw new Exception("CP560."._("Erreur : Aucun 
compte parent ")."[$v]");
+                        }
+
+                        continue;
+                    }
+                }
+                // TVA
+                if ($value->ad_id==ATTR_DEF_TVA)
+                {
+                    // Verify if the rate exists, if not then do not update
+                    if (strlen(trim($value->av_text))!=0)
+                    {
+                        if ($p_fiche->cn->get_value("select count(*) from 
tva_rate where tva_id=$1",[$value->av_text])==0)
+                        {
+                            continue;
+                        }
+                    }
+                }
+                // Normal traitement
+                $sql="update fiche_detail set ad_value=$1 where jft_id=$2";
+                $p_fiche->cn->exec_sql($sql, 
array(strip_tags($value->av_text), $jft_id));
+            }
+            if ($commit)
+            {
+                $p_fiche->cn->commit();
+            }
+        }
+        catch (Exception $e)
+        {
+            echo '<span class="error">'.
+            $e->getMessage().
+            '</span>';
+            record_log("CP597.".$e->getMessage().$e->getTraceAsString());
+           if ($commit) {         $p_fiche->cn->rollback(); }
+            return;
+        }
+       
+        return;
+    }
+
+}
diff --git a/include/class/fiche.class.php b/include/class/fiche.class.php
index cb53653..c947256 100644
--- a/include/class/fiche.class.php
+++ b/include/class/fiche.class.php
@@ -51,6 +51,10 @@ class Fiche
         $this->quick_code='';
         $this->attribut=array();
         $this->f_enable='1';
+        if ($p_id != 0 ) { $this->load();} else {
+            $this->fiche_def=0;
+        }
+        
        
     }
     public function get_id()
@@ -160,6 +164,7 @@ class Fiche
     function setAttribut($p_ad_id,$p_value)
     {
         if ( sizeof($this->attribut)==0 ) $this->getAttribut();
+        
         for ($e=0;$e <sizeof($this->attribut);$e++)
         {
             if ( $this->attribut[$e]->ad_id == $p_ad_id )
@@ -175,72 +180,7 @@ class Fiche
      */
     function getAttribut()
     {
-        if ( $this->id == 0)
-        {
-            return;
-        }
-        $sql="select *
-             from
-                   fiche
-             natural join fiche_detail
-            join jnt_fic_attr on (jnt_fic_attr.fd_id=fiche.fd_id and 
fiche_detail.ad_id=jnt_fic_attr.ad_id)
-             join attr_def on (attr_def.ad_id=fiche_detail.ad_id) where f_id= 
$1".
-             " order by jnt_order";
-
-        $Ret=$this->cn->exec_sql($sql,[$this->id]);
-        if ( ($Max=Database::num_row($Ret)) == 0 )
-            return ;
-        for ($i=0;$i<$Max;$i++)
-        {
-            $row=Database::fetch_array($Ret,$i);
-            $this->fiche_def=$row['fd_id'];
-            $this->f_enable=$row['f_enable'];
-            $t=new Fiche_Attr ($this->cn);
-            $t->ad_id=$row['ad_id'];
-            $t->ad_text=$row['ad_text'];
-            $t->av_text=$row['ad_value'];
-            $t->ad_type=$row['ad_type'];
-            $t->ad_size=$row['ad_size'];
-            $t->ad_extra=$row['ad_extra'];
-            $t->jnt_order=$row['jnt_order'];
-            $this->attribut[$i]=$t;
-        }
-        $e=new Fiche_Def($this->cn,$this->fiche_def);
-        $e->GetAttribut();
-
-        if ( sizeof($this->attribut) != sizeof($e->attribut ) )
-        {
-
-            /*
-                        *!! Missing attribute
-                        */
-            foreach ($e->attribut as $f )
-            {
-                $flag=0;
-                foreach ($this->attribut as $g )
-                {
-                    if ( $g->ad_id == $f->ad_id )
-                        $flag=1;
-                }
-                if ( $flag == 0 )
-                {
-                    // there's a missing one, we insert it
-                    $t=new Fiche_Attr ($this->cn,$f->ad_id);
-                    $t->av_text="";
-                    $t->ad_text=$f->ad_text;
-                    $t->jnt_order=$f->jnt_order;
-                    $t->ad_type=$f->ad_type;
-                    $t->ad_size=$f->ad_size;
-                    $t->ad_id=$f->ad_id;
-                    $t->ad_extra=$f->ad_extra;
-                    $this->attribut[$Max]=$t;
-                    $Max++;
-                } // if flag == 0
-
-            }// foreach
-
-
-        }//missing attribut
+        Card_Property::load($this);
     }
     /**
      * @brief find the card with the p_attribut equal to p_value, it is not 
case sensitive
@@ -257,20 +197,6 @@ class Fiche
     }
 
     /*!
-     * \brief give the size of a card object
-     *
-     * \return size
-     */
-    function size()
-    {
-        if ( isset ($this->ad_id))
-            return sizeof($this->ad_id);
-        else
-            return 0;
-    }
-
-
-    /*!
      **************************************************
      * \brief  Count the nb of card with the reference card id frd_id
      *
@@ -307,10 +233,11 @@ class Fiche
     }
     /*!
      **************************************************
-     * \brief  Return array of card from the frd family
-     *
+     * \brief  Return array of card from the frd family deprecated , use 
insert get_by_category_id
      *
-     * \param  $p_frd_id the fiche_def_ref.frd_id
+     * \deprecated 
+     * \see Fiche::get_by_category
+     * \param  $p_frd_id the fiche_def_ref.frd_id NOT USED , 
$this->fiche_def_ref will be used instead
      * \param  $p_offset
      * \param  $p_search is an optional filter
      *\param $p_order : possible values are name, f_id
@@ -318,49 +245,7 @@ class Fiche
      */
     function GetByDef($p_frd_id,$p_offset=-1,$p_search="",$p_order='')
     {
-        switch($p_order)
-        {
-        case 'name' :
-                $order=' order by name';
-            break;
-        case 'f_id':
-            $order='order by f_id';
-            break;
-        default:
-            $order='';
-        }
-        if ( $p_offset == -1 )
-        {
-            $sql="select *
-                 from
-                 fiche join fiche_Def using (fd_id) join vw_fiche_name 
using(f_id)
-                 where frd_id=".$p_frd_id." $p_search ".$order;
-        }
-        else
-        {
-            $limit=($_SESSION[SESSION_KEY.'g_pagesize']!=-1)?"limit 
".$_SESSION[SESSION_KEY.'g_pagesize']:"";
-            $sql="select *
-                 from
-                 fiche join fiche_Def using (fd_id) join vw_fiche_name 
using(f_id)
-                 where frd_id=".$p_frd_id." $p_search $order  "
-                 .$limit." offset ".$p_offset;
-
-        }
-
-        $Ret=$this->cn->exec_sql($sql);
-        if ( ($Max=Database::num_row($Ret)) == 0 )
-            return [];
-        $all[0]=new Fiche($this->cn);
-
-        for ($i=0;$i<$Max;$i++)
-        {
-            $row=Database::fetch_array($Ret,$i);
-            $t=new Fiche($this->cn,$row['f_id']);
-            $t->getAttribut();
-            $all[$i]=clone $t;
-
-        }
-        return $all;
+           return $this->get_by_category($p_offset,$p_search,$p_order);
     }
     function ShowTable()
     {
@@ -381,23 +266,11 @@ class Fiche
      */
     function strAttribut($p_ad_id,$p_return=1)
     {
-               $return=($p_return==1)?NOTFOUND:"";
-        if ( sizeof ($this->attribut) == 0 )
+        $return=($p_return==1)?NOTFOUND:"";
+        if ( empty ($this->attribut)  )
         {
 
-            if ($this->id==0) {
-                                       return $return;
-                       }
-            // object is not in memory we need to look into the database
-            $sql="select ad_value from fiche_detail
-                 where f_id= $1  and ad_id= $2 ";
-            $Res=$this->cn->exec_sql($sql,array($this->id,$p_ad_id));
-            $row=Database::fetch_all($Res);
-            // if not found return error
-            if ( $row == false )
-                return $return;
-
-            return $row[0]['ad_value'];
+          $this->getAttribut();
         }
 
         foreach ($this->attribut as $e)
@@ -414,18 +287,13 @@ class Fiche
      */
     function to_array()
     {
-        $array=$this->cn->get_array("select 'av_text'||fd.ad_id::text \"key\", 
ad_value
-            from fiche_detail  fd
-                join fiche  f using (f_id)
-                join jnt_fic_attr jfa using (fd_id,ad_id)
-            where 
-                f.f_id=$1
-                order by jfa.jnt_order",[$this->id]);
-        if ( empty ($array) ) return array();
         $a_return=[];
-        foreach ($array as $row) {
-            $key=$row['key'];$value=$row['ad_value'];
-            $a_return[$key]=$value;
+        if ( empty ($this->attribut)) {
+            $this->getAttribut();
+        }
+        foreach ($this->attribut as $attr)
+        {
+            $a_return['av_text'.$attr->ad_id]=$attr->av_text;
         }
         $a_return['f_enable']=$this->get_f_enable();
         return $a_return;
@@ -441,128 +309,17 @@ class Fiche
     function blank($p_fiche_def)
     {
         // array = array of attribute object sorted on ad_id
-        $f=new Fiche_Def($this->cn,$p_fiche_def);
-        $f->get();
-        $array=$f->getAttribut();
+        $fiche_def=new Fiche_Def($this->cn,$p_fiche_def);
+        $fiche_def->get();
+        $array=$fiche_def->getAttribut();
         $r="";
         $r.='<table style="width:98%;margin:1%">';
         foreach ($array as $attr)
         {
             $table=0;
             $msg="";$bulle='';
-            if ( $attr->ad_id == ATTR_DEF_ACCOUNT)
-            {
-                $w=new IPoste("av_text".$attr->ad_id);
-                $w->set_attribute('ipopup','ipop_account');
-                $w->set_attribute('jrn','0');
-                $w->set_attribute('account',"av_text".$attr->ad_id);
-                               $w->dbl_click_history();
-                //  account created automatically
-                $sql="select account_auto($p_fiche_def)";
-                $ret_sql=$this->cn->exec_sql($sql);
-                $a=Database::fetch_array($ret_sql,0);
-                $label=new ISpan();
-                $label->name="av_text".$attr->ad_id."_label";
-
-                if ( $a['account_auto'] == 't' )
-                    $msg.=$label->input()." <span style=\"color:red\">".
-                                               _("Rappel: Poste créé 
automatiquement à partir de ")
-                                               .$f->class_base." </span> ";
-                else
-                {
-                    // if there is a class base in fiche_def_ref, this account 
will be the
-                    // the default one
-                    if ( strlen(trim($f->class_base)) != 0 )
-                    {
-                        $msg.="<TD>".$label->input()." <span 
style=\"color:red\">"._("Rappel: Poste par défaut sera ").
-                              $f->class_base.
-                              " !</span> ";
-                        $w->value=$f->class_base;
-                    }
-
-                }
-                $r.="<TR>".td(_("Poste Comptable"),' class="highlight 
input_text" ' ).td($w->input().$msg)."</TR>";
-                continue;
-            }
-            elseif ( $attr->ad_id == ATTR_DEF_TVA)
-            {
-                $w=new ITva_Popup('popup_tva');
-                $w->table=1;
-            }
-
-            else
-            {
-             switch ($attr->ad_type)
-                {
-                    case 'text':
-                            $w = new IText();
-                            $w->css_size = "100%";
-                            break;
-                    case 'numeric':
-                            $w = new INum();
-                            $w->prec=($attr->ad_extra=="")?2:$attr->ad_extra;
-                            $w->size = $attr->ad_size;
-                            break;
-                    case 'date':
-                            $w = new IDate();
-                            break;
-                    case 'zone':
-                            $w = new ITextArea();
-                            $w->style=' class="itextarea" 
style="margin:0px;width:100%"';
-                            break;
-                    case 'poste':
-                            $w = new IPoste("av_text" . $attr->ad_id);
-                            $w->set_attribute('ipopup', 'ipop_account');
-                            $w->set_attribute('account', "av_text" . 
$attr->ad_id);
-                            $w->table = 1;
-                            $bulle = Icon_Action::infobulle(14);
-                            break;
-                    case 'check':
-                            $w=new InputSwitch("av_text".$attr->ad_id);
-                            $w->value=(trim($w->value)=="")?1:$w->value;
-                            break;
-                    case 'select':
-                            $w = new ISelect("av_text" . $attr->ad_id);
-                            $w->value = $this->cn->make_array($attr->ad_extra);
-                            $w->style= 'style="width:100%"';
-                            break;
-                    case 'card':
-                            $w = new ICard("av_text" . $attr->ad_id);
-                            // filter on frd_id
-                            $w->extra = $attr->ad_extra;
-                            $w->extra2 = 0;
-                            $label = new ISpan();
-                            $label->name = "av_text" . $attr->ad_id . "_label";
-                            $w->set_attribute('ipopup', 'ipopcard');
-                            $w->set_attribute('typecard', $attr->ad_extra);
-                            $w->set_attribute('inp', "av_text" . $attr->ad_id);
-                            $w->set_attribute('label', "av_text" . 
$attr->ad_id . "_label");
-                            $msg = $w->search();
-                            $msg.=$label->input();
-                            break;
-                }
-                $w->table = 0;
-            }
-            $w->table = $table;
-            $w->label = $attr->ad_text;
-            $w->name = "av_text" . $attr->ad_id;
-            if ($attr->ad_id == 21 || 
$attr->ad_id==22||$attr->ad_id==20||$attr->ad_id==31)
-            {
-                    $bulle=Icon_Action::infobulle( 21);
-            }
-
-            // Warning length quickcode
-            if ($attr->ad_id== ATTR_DEF_QUICKCODE ) {
-                $bulle=Icon_Action::warnbulle(76);
-            }
-            if ($attr->ad_id == ATTR_DEF_NAME || $attr->ad_id== 
ATTR_DEF_QUICKCODE)
-            {
-                $class=" input_text highlight info";
-
-            }
-            else
-                $class="input_text";
-            $r.="<TR>" . td(_($w->label)." $bulle", ' class="'.$class.'" ') . 
td($w->input()." $msg")." </TR>";
+            $r.=$attr->input($fiche_def);
+           
         }
         $r.= '</table>';
         return $r;
@@ -602,6 +359,7 @@ class Fiche
             return 'FNT';
         }
         
+        $fiche_def=new Fiche_Def($this->cn,$this->fiche_def);
         /* for each attribute */
         foreach ($attr as $r)
         {
@@ -609,168 +367,14 @@ class Fiche
             $bulle="";
             if ($p_readonly)
             {
-                $w=new IText();
-                $w->table=1;
-                $w->readOnly=true;
-                $w->css_size="100%";
+                $ret .= $r->print();
             }
             if ($p_readonly==false)
             {
-
-                if ($r->ad_id==ATTR_DEF_ACCOUNT)
-                {
-                    $w=new IPoste("av_text".$r->ad_id);
-                    $w->id=$p_in."av_text".$r->ad_id;
-                    $w->set_attribute('ipopup', 'ipop_account');
-                    $w->set_attribute('account', $w->id);
-                    $w->set_attribute('jrn','0');
-                    $w->dbl_click_history();
-                    //  account created automatically
-                    $w->table=0;
-                    $w->value=$r->av_text;
-                    //  account created automatically
-                    $sql="select account_auto($this->fiche_def)";
-                    $ret_sql=$this->cn->exec_sql($sql);
-                    $a=Database::fetch_array($ret_sql, 0);
-                    $bulle=Icon_Action::infobulle(10);
-
-                    if ($a['account_auto']=='t')
-                        $bulle.=Icon_Action::warnbulle(11);
-                }
-                elseif ($r->ad_id==ATTR_DEF_TVA)
-                {
-                    $w=new ITva_Popup('popup_tva');
-                    $w->table=1;
-                    $w->value=$r->av_text;
-                }
-                else
-                {
-                    switch ($r->ad_type)
-                    {
-                        case 'text':
-                            $w=new IText('av_text'.$r->ad_id);
-                            $w->css_size="100%";
-                            $w->value=$r->av_text;
-                            break;
-                        case 'numeric':
-                            $w=new INum('av_text'.$r->ad_id);
-                            $w->size=$r->ad_size;
-                            $w->prec=($r->ad_extra=="")?2:$r->ad_extra;
-                            $w->value=$r->av_text;
-                            break;
-                        case 'date':
-                            $w=new IDate('av_text'.$r->ad_id);
-                            $w->value=$r->av_text;
-                            break;
-                        case 'zone':
-                            $w=new ITextArea('av_text'.$r->ad_id);
-                            $w->style=' class="itextarea" 
style="margin:0px;width:100%"';
-                            $w->value=$r->av_text;
-                            break;
-                        case 'check':
-                            $w=new InputSwitch("av_text".$r->ad_id);
-                             $w->value=$r->av_text;
-                            $w->value=(trim($w->value)=="")?1:$w->value;
-                            break;
-                        case 'poste':
-                            $w=new IPoste("av_text".$r->ad_id);
-                            $w->set_attribute('ipopup', 'ipop_account');
-                            $w->set_attribute('account', "av_text".$r->ad_id);
-                            $w->dbl_click_history();
-                            $w->width=$r->ad_size;
-                            $w->table=0;
-                            $bulle=Icon_Action::infobulle(14);
-                            $w->value=$r->av_text;
-                            $w->set_attribute('jrn','0');
-                            break;
-                        case 'card':
-                            $uniq=rand(0, 1000);
-                            $w=new ICard("av_text".$r->ad_id);
-                            $w->id="card_".$this->id.$uniq;
-                            // filter on ad_extra
-
-                            $filter=$r->ad_extra;
-                            $w->width=$r->ad_size;
-                            $w->extra=$filter;
-                            $w->extra2=0;
-                            $w->limit=6;
-                            $label=new ISpan();
-                            $label->name="av_text".$r->ad_id.$uniq."_label";
-                            $fiche=new Fiche($this->cn);
-                            $fiche->get_by_qcode($r->av_text);
-                            if ($fiche->id==0)
-                            {
-                                $label->value=(trim($r->av_text)=='')?"":" 
"._("Fiche non trouvée")." ";
-                                $r->av_text="";
-                            }
-                            else
-                            {
-                                
$label->value=$fiche->strAttribut(ATTR_DEF_NAME).
-                                        " ".
-                                        
$fiche->strAttribut(ATTR_DEF_FIRST_NAME,0);
-                            }
-                            $w->set_attribute('ipopup', 'ipopcard');
-                            $w->set_attribute('typecard', $filter);
-                            $w->set_attribute('inp', $w->id);
-                            $w->set_attribute('label', $label->name);
-                            $w->autocomplete=1;
-                            $w->dblclick="fill_ipopcard(this);";
-                            $msg=$w->search();
-                            $msg.=$label->input();
-                            $w->value=$r->av_text;
-                            break;
-                        case 'select':
-                            $w=new ISelect();
-                            $w->value=$this->cn->make_array($r->ad_extra);
-                            $w->selected=$r->av_text;
-                            $w->style=' style="width:100%" ';
-                            break;
-                        default:
-                            var_dump($r);
-                            throw new Exception("Type invalide");
-                    }
-                    $w->table=0;
-                }
-            }
-            else
-            {
-                switch ($r->ad_type)
-                {
-                    case 'select':
-                        $x=new ISelect();
-                        $x->value=$this->cn->make_array($r->ad_extra);
-                        $x->selected=$r->av_text;
-                        $value=$x->display();
-                        $w->value=$value;
-                        break;
-                    case 'check':
-                       $w=new InputSwitch("av_text".$r->ad_id);
-                       $w->value=$r->av_text;
-                       $w->value=(trim($w->value)=="")?1:$w->value;
-                       break;
-                    default:
-                        $w->value=$r->av_text;
-                }
+                $ret .= $r->input($fiche_def);
+               
             }
-
-            $w->name="av_text".$r->ad_id;
-            $w->readOnly=$p_readonly;
-
-            if ($r->ad_id==21||$r->ad_id==22||$r->ad_id==20||$r->ad_id==31)
-            {
-                $bulle=Icon_Action::infobulle(21);
-            }
-            if ($r->ad_id == ATTR_DEF_NAME || $r->ad_id== 
ATTR_DEF_QUICKCODE||$r->ad_id==ATTR_DEF_ACCOUNT) 
-                $class=" input_text highlight info";
-            else
-                $class="input_text";
-
-            // Warning length quickcode
-            if ($r->ad_id== ATTR_DEF_QUICKCODE ) {
-                $bulle=Icon_Action::warnbulle(76);
-            }
-            
$ret.="<TR>".td(_($r->ad_text).$bulle,'class="'.$class.'"').td($w->input()." 
".$msg)." </TR>";
-        }
+         }
         // Display if the card is enable or not
         $enable_is=new InputSwitch("f_enable");
         $enable_is->value=$this->f_enable;
@@ -787,11 +391,12 @@ class Fiche
     }
 
     /*!
-     * \brief  Save a card, call insert or update
+     * \brief  Save a card, call insert or update 
+     * \see Fiche::insert , Fiche::update
      *
      * \param p_fiche_def (default 0)
      */
-    function Save($p_fiche_def=0)
+    function save($p_fiche_def=0)
     {
         // new card or only a update ?
         if ( $this->id == 0 )
@@ -800,13 +405,13 @@ class Fiche
             $this->update();
     }
     /*!
-     * \brief  insert a new record
+     * \brief  insert a new record thanks an array , either as parameter or 
$_POST
      *
      * \param $p_fiche_def fiche_def.fd_id
      * \param $p_array is the array containing the data
-     *\param $transation if we want to manage the transaction in this function
-     * true for small insert and false for a larger loading, the BEGIN / 
COMMIT sql
-     * must be done into the caller
+     *\param $transation DEPRECATED : if we are in a transaction, we don't 
commit here , else if not, the
+     * then a transaction is started and committed 
+     * 
      av_textX where X is the ad_id
      *\verb
     example
@@ -820,18 +425,9 @@ class Fiche
 
         $fiche_id=$this->cn->get_next_seq('s_fiche');
         $this->id=$fiche_id;
-        // first we create the card
-        if ($transaction)
-            $this->cn->start();
-        /*
-         * Sort the array for having the name BEFORE the quickcode and the 
-         * Accounting
-         */
-        ksort($p_array);
-       $name="";
+        $this->fiche_def=$p_fiche_def;
         try
         {
-            $this->cn->start();
 
             // by default the card is available
             if ( !isset ($p_array['f_enable'])) {
@@ -839,360 +435,64 @@ class Fiche
             }
             $Ret=$this->cn->exec_sql("insert into fiche(f_id,f_enable,fd_id) 
values ($1,$2,$3)",
                     array($fiche_id, $p_array['f_enable'],$p_fiche_def));
-            
-            // parse the $p_array array
-            foreach ($p_array as $name=> $value)
-            {
-                /* avoid the button for searching an accounting item */
-                if (preg_match('/^av_text[0-9]+$/', $name)==0)
-                    continue;
-
-                list ($id)=sscanf($name, "av_text%d");
-                if ($id==null)
-                    continue;
-
-                // Special traitement
-                // quickcode
-                if ($id==ATTR_DEF_QUICKCODE)
-                {
-                    $sql=sprintf("select insert_quick_code(%d,'%s')", 
$fiche_id,
-                            sql_string($value));
-                    $this->cn->exec_sql($sql);
-                    continue;
-                }
-                // name
-                if ($id==ATTR_DEF_NAME)
-                {
-                    if (strlen(trim($value))==0)
-                        $value="pas de nom";
-                   $account_name=$value;
-
-                }
-                // account
-                if ($id==ATTR_DEF_ACCOUNT)
-                {
-                    $v=mb_strtoupper($value);
-                    try
-                    {
-                        // Check that the accounting can be used directly
-                        if (strlen(trim($v))!=0)
-                        {
-                            if (strpos($value, ',')==0)
-                            {
-                             if ( mb_strlen($value)>40) throw new Exception 
(_("Poste comptable trop long"), 1);
-                             
-                             $v=$this->cn->get_value("select 
format_account($1)",
-                                                     array($value));
-                             $acc_account=new Acc_Account($this->cn,$v);
-                                
-                             if ($acc_account->get_parameter("id")== -1 ) {
-                                    $acc_account->set_parameter("pcm_lib", 
$account_name);
-                                   // By Default can be used directly
-                                    
$acc_account->set_parameter('pcm_direct_use',"Y") ;
-                                    $parent=$acc_account->find_parent();
-                                    
$acc_account->set_parameter("pcm_val_parent",$parent);
-                                    $acc_account->save();
-                                } 
-                                else
-                                {
-                                    // Check that the accounting can be used 
directly
-                                    
-                                    if 
($acc_account->get_parameter('pcm_direct_use') == 'N') {
-                                        throw new Exception(_("Utilisation 
directe interdite du poste comptable $v"));
-                                    }
-
-                             }
-                             
-                                }
-                            else
-                            {
-                                                              
-                                $ac_array=explode(",", $value);
-                                if (count($ac_array)<>2)
-                                    throw new Exception(_('Désolé, il y a trop 
de virgule dans le poste comptable ').h($value));
-                                
-                                $part1=$ac_array[0];
-                                $part2=$ac_array[1];
-                               
-                               if ( mb_strlen($part1)>40) throw new Exception 
(_("Poste comptable trop long"), 1);
-                                if ( mb_strlen($part2)>40) throw new Exception 
(_("Poste comptable trop long"), 1);
-
-                               $part1=$this->cn->get_value('select 
format_account($1)',
-                                        array($part1));
-                                $part2=$this->cn->get_value('select 
format_account($1)',
-                                        array($part2));
-                               
-                               // Check that the accounting can be used 
directly
-                                $acc_account1=new 
Acc_Account($this->cn,$part1);
-                                if ($acc_account1->get_parameter("id")== -1 ) {
-                                    $acc_account1->set_parameter("pcm_lib", 
$account_name);
-                                    
$acc_account1->set_parameter('pcm_direct_use',"Y") ;
-                                    $parent=$acc_account1->find_parent();
-                                    
$acc_account1->set_parameter("pcm_val_parent",$parent);
-                                    $acc_account1->save();
-                                } else if 
($acc_account1->get_parameter('pcm_direct_use') == 'N') {
-                                    throw new Exception(_("Utilisation directe 
interdite du poste comptable $part1"));
-                                }
-                                // Check that the accounting can be used 
directly
-                                $acc_account2=new 
Acc_Account($this->cn,$part2);
-                                if ($acc_account2->get_parameter("id")== -1 ) {
-                                    $acc_account2->set_parameter("pcm_lib", 
$account_name);
-                                    
$acc_account2->set_parameter('pcm_direct_use',"Y") ;
-                                    $parent=$acc_account2->find_parent();
-                                    
$acc_account2->set_parameter("pcm_val_parent",$parent);
-                                    $acc_account2->save();
-                                } else if 
($acc_account2->get_parameter('pcm_direct_use') == 'N') {
-                                    throw new Exception(_("Utilisation directe 
interdite du poste comptable $part2"));
-                                }
-                                $v=$part1.','.$part2;
-
-                            }
-                            $parameter=array($this->id, $v);
-                        }
-                        else
-                        {
-                            $parameter=array($this->id, null);
-                        }
-                        $v=$this->cn->get_value("select account_insert($1,$2)",
-                                $parameter);
-                    }
-                    catch (Exception $e)
-                    {
-                        throw ($e);
-                    }
-                    continue;
-                }
-                // TVA
-                if ($id==ATTR_DEF_TVA)
-                {
-                    // Verify if the rate exists, if not then do not update
-                    if (strlen(trim($value))!=0)
-                    {
-                        if (isNumber($value)==0)
-                            continue;
-                        if ($this->cn->count_sql("select * from tva_rate where 
tva_id=".$value)==0)
-                        {
-                            continue;
-                        }
-                    }
-                }
-                // Normal traitement
-                $value2=sql_string($value);
-
-                $sql=sprintf("select attribut_insert(%d,%d,'%s')", $fiche_id,
-                        $id, strip_tags(trim($value2)));
-                $this->cn->exec_sql($sql);
+            // compute a quick_code
+            if ( ! isset ($p_array["av_text".ATTR_DEF_QUICKCODE]  )) {
+                $p_array["av_text".ATTR_DEF_QUICKCODE]="";
             }
-            $this->cn->commit();
+            $sql=sprintf("select insert_quick_code(%d,'%s')", $fiche_id,
+                            
sql_string($p_array['av_text'.ATTR_DEF_QUICKCODE]));
+            $this->getAttribut();
+            Card_Property::update($this);
         }
         catch (Exception $e)
         {
-            record_log($e->getMessage()." ".$e->getTraceAsString());
+            record_log("FIC603".$e->getMessage()." ".$e->getTraceAsString());
             $this->cn->rollback();
             throw ($e);
             return;
         }
-        if ($transaction)
-            $this->cn->commit();
         return;
     }
 
     /*!
      * \brief update a card with an array
+     * \param $p_array (optional) is the array containing the data , if NULL 
then $_POST will be uses
+     *\param $transation if we want to manage the transaction in this function
+     * true for small insert and false for a larger loading, the BEGIN / 
COMMIT sql
+     * must be done into the caller
+     av_textX where X is the ad_id
+     *\verb
+    example
+    av_text1=>'name'
+    \endverb
      */
     function update($p_array=null)
     {
-        global $g_user;
         if ($p_array==null)
-            $p_array=$_POST;
-
-        try
         {
-            $this->cn->start();
-            
-            $this->cn->exec_sql("update fiche set f_enable=$1 where 
f_id=$2",array($p_array['f_enable'],$this->id));
-            
-            // parse the $p_array array
-            foreach ($p_array as $name=> $value)
-            {
-                if (preg_match('/^av_text[0-9]+$/', $name)==0)
-                    continue;
-
-                list ($id)=sscanf($name, "av_text%d");
-
-                if ($id==null)
-                    continue;
-
-                // retrieve jft_id to update table attr_value
-               $sql=" select jft_id from fiche_detail where ad_id=$1 and 
f_id=$2";
-                $Ret=$this->cn->exec_sql($sql,[$id,$this->id]);
-
-               if (Database::num_row($Ret)==0)
-                {
-                    // we need to insert this new attribut
-                    $jft_id=$this->cn->get_next_seq('s_jnt_fic_att_value');
-
-                    $sql2="insert into 
fiche_detail(jft_id,ad_id,f_id,ad_value) values ($1,$2,$3,NULL)";
-
-                    $ret2=$this->cn->exec_sql($sql2,
-                            array($jft_id, $id, $this->id));
-                }
-                else
-                {
-                    $tmp=Database::fetch_array($Ret, 0);
-                    $jft_id=$tmp['jft_id'];
-                }
-                // Special traitement
-                // quickcode
-                if ($id==ATTR_DEF_QUICKCODE)
-                {
-                    $sql=sprintf("select update_quick_code(%d,'%s')", $jft_id,
-                            sql_string($value));
-                    $this->cn->exec_sql($sql);
-                    continue;
-                }
-                // name
-                if ($id==ATTR_DEF_NAME)
-                {
-                    if (strlen(trim($value))==0)
-                        continue;
-                }
-                // account
-                if ($id==ATTR_DEF_ACCOUNT)
-                {
-                 $v=mb_strtoupper($value);
-
-                   if (trim($v)!='')
-                    {
-                        if (strpos($v, ',')!=0)
-                        {
-                            $ac_array=explode(",", $v);
-                            if (count($ac_array)<>2)
-                                throw new Exception('Désolé, il y a trop de 
virgule dans le poste comptable '.h($v));
-                            $part1=$ac_array[0];
-                            $part2=$ac_array[1];
-                           $part1=$this->cn->get_value('select 
format_account($1)',
-                                    array($part1));
-                            $part2=$this->cn->get_value('select 
format_account($1)',
-                                    array($part2));
-
-                           if ( mb_strlen($part1)>40) throw new Exception 
(_("Poste comptable trop long"), 1);
-                            if ( mb_strlen($part2)>40) throw new Exception 
(_("Poste comptable trop long"), 1);
-                            
-                            $part1=$this->cn->get_value('select 
format_account($1)',    array($part1));
-                            $acc_account1=new Acc_Account($this->cn,$part1);
-                            
-                            if ($acc_account1->get_parameter("id")== -1 ) {
-                                
$account_name=$this->strAttribut(ATTR_DEF_NAME);
-                                $acc_account1->set_parameter("pcm_lib", 
$account_name);
-                                
$acc_account1->set_parameter('pcm_direct_use',"Y") ;
-                                $parent=$acc_account1->find_parent();
-                                
$acc_account1->set_parameter("pcm_val_parent",$parent);
-                                $acc_account1->save();
-                            }
-                            // Check that the accounting can be used directly
-                            if ($acc_account1->get_parameter('pcm_direct_use') 
== 'N') {
-                                throw new Exception(_("Utilisation directe 
interdite du poste comptable $part1"));
-                            }
-                            // Part 2
-                            $part2=$this->cn->get_value('select 
format_account($1)',
-                                    array($part2));
-                            $acc_account2=new Acc_Account($this->cn,$part2);
-                            
-                                                                               
     
-                            if ($acc_account2->get_parameter("id")== -1 ) {
-                                    
$account_name=$this->strAttribut(ATTR_DEF_NAME);
-                                    $acc_account2->set_parameter("pcm_lib", 
$account_name);
-                                    
$acc_account2->set_parameter('pcm_direct_use',"Y") ;
-                                    $parent=$acc_account2->find_parent();
-                                    
$acc_account2->set_parameter("pcm_val_parent",$parent);
-                                    $acc_account2->save();
-                            }
-
-                            // Check that the accounting can be used directly
-                            if ($acc_account2->get_parameter('pcm_direct_use') 
== 'N') {
-                                throw new Exception(_("Utilisation directe 
interdite du poste comptable $part2"));
-                            }
-                            $v=$part1.','.$part2;
-                        }
-                        else
-                        {
-                            if ( mb_strlen($v)>40) throw new Exception 
(_("Poste comptable trop long"), 1);
-                            $acc_account=new Acc_Account($this->cn,$v);
-                            // Set default for new accounting
-                             if ($acc_account->get_parameter("id")== -1 ) {
-                                    
$account_name=$this->strAttribut(ATTR_DEF_NAME);
-                                    $acc_account->set_parameter("pcm_lib", 
$account_name);
-                                   // By Default can be used directly
-                                    
$acc_account->set_parameter('pcm_direct_use',"Y") ;
-                                    $parent=$acc_account->find_parent();
-                                    
$acc_account->set_parameter("pcm_val_parent",$parent);
-                                    $acc_account->save();
-                                }
-                            
-                            $acc_account=new Acc_Account($this->cn,$v);
-                            if ($acc_account->get_parameter('pcm_direct_use') 
== 'N') {
-                                throw new Exception(_("Utilisation directe 
interdite du poste comptable $v"));
-                            }
-                        }
-                        $sql=sprintf("select account_update(%d,'%s')",
-                                $this->id, $v);
-                        try
-                        {
-                            $this->cn->exec_sql($sql);
-                        }
-                        catch (Exception $e)
-                        {
-                            throw new Exception(_("opération annulée")." 
".$e->getMessage());
-                        }
-                        continue;
-                    }
-                    if (strlen(trim($v))==0)
-                    {
-
-                        $sql=sprintf("select account_update(%d,null)", 
$this->id);
-                        try
-                        {
-                            $Ret=$this->cn->exec_sql($sql);
-                        }
-                        catch (Exception $e)
-                        {
-                            throw new Exception(__LINE__."Erreur : ce compte 
[$v] n'a pas de compte parent.".
-                            "L'opération est annulée");
-                        }
-
-                        continue;
-                    }
-                }
-                // TVA
-                if ($id==ATTR_DEF_TVA)
-                {
-                    // Verify if the rate exists, if not then do not update
-                    if (strlen(trim($value))!=0)
-                    {
-                        if ($this->cn->count_sql("select * from tva_rate where 
tva_id=".$value)==0)
-                        {
-                            continue;
-                        }
-                    }
-                }
-                // Normal traitement
-                $sql="update fiche_detail set ad_value=$1 where jft_id=$2";
-                $this->cn->exec_sql($sql, array(strip_tags($value), $jft_id));
-            }
+            $p_array=$_POST;
         }
-        catch (Exception $e)
-        {
-            echo '<span class="error">'.
-            $e->getMessage().
-            '</span>';
-            record_log($e->getMessage());
-              record_log($e);
-            $this->cn->rollback();
-            return;
+        $this->fiche_def = $this->cn->get_value("select fd_id from fiche where 
f_id=$1",[$this->id]);
+        if ( $this->cn->size()==0) {
+            throw new Exception("FICHE.UPDATE01"._("Fiche n'existe 
pas"),EXC_INVALID);
         }
-        $this->cn->commit();
-        return;
+        
+        
+        // get the card properties for this card category
+        $this->getAttribut();
+        
+        if ( empty ($this->attribut) ) {
+            throw new Exception("FICHE.UPDATE02"._("Aucun attribut 
")."($fiche_def)",EXC_INVALID);
+        }
+        // for each property set the attribut on the card
+        foreach($this->attribut as $property) {
+            $key='av_text'.$property->ad_id;
+            if ( isset($p_array[$key])) {
+                $this->setAttribut($property->ad_id, $p_array[$key]);
+            }
+        }
+        // save all
+        Card_Property::update($this);
     }
 
     /*!\brief  remove a card
@@ -1262,14 +562,57 @@ class Fiche
     {
         $this->getAttribut();
     }
-    /*!\brief get all the card thanks the fiche_def_ref
+    /*!
+     * \brief get all the card thanks the fiche_def_ref
      * \param $p_offset (default =-1)
      * \param $p_search sql condition
      * \return array of fiche object
      */
     function get_by_category($p_offset=-1,$p_search="",$p_order='')
     {
-        return 
fiche::GetByDef($this->fiche_def_ref,$p_offset,$p_search,$p_order);
+       switch($p_order)
+        {
+        case 'name' :
+                $order=' order by name';
+            break;
+        case 'f_id':
+            $order='order by f_id';
+            break;
+        default:
+            $order='';
+        }
+        if ( $p_offset == -1 )
+        {
+            $sql="select *
+                 from
+                 fiche join fiche_Def using (fd_id) join vw_fiche_name 
using(f_id)
+                 where frd_id=".$this->fiche_def_ref." $p_search ".$order;
+        }
+        else
+        {
+            $limit=($_SESSION[SESSION_KEY.'g_pagesize']!=-1)?"limit 
".$_SESSION[SESSION_KEY.'g_pagesize']:"";
+            $sql="select *
+                 from
+                 fiche join fiche_Def using (fd_id) join vw_fiche_name 
using(f_id)
+                 where frd_id=".$this->fiche_def_ref." $p_search $order  "
+                 .$limit." offset ".$p_offset;
+
+        }
+
+        $Ret=$this->cn->exec_sql($sql);
+        if ( ($Max=Database::num_row($Ret)) == 0 )
+            return [];
+        $all[0]=new Fiche($this->cn);
+
+        for ($i=0;$i<$Max;$i++)
+        {
+            $row=Database::fetch_array($Ret,$i);
+            $t=new Fiche($this->cn,$row['f_id']);
+            $t->getAttribut();
+            $all[$i]=clone $t;
+
+        }
+        return $all;
     }
     /*!\brief retrieve the frd_id of the fiche it is the type of the
      *        card (bank, purchase...)
@@ -2337,7 +1680,7 @@ class Fiche
 
        function get_gestion_title()
        {
-               $r = "<h2 id=\"gestion_title\">" . h($this->getName()) . " " . 
h($this->getAttribut(ATTR_DEF_FIRST_NAME)) . '[' . $this->get_quick_code() . 
']</h2>';
+               $r = "<h2 id=\"gestion_title\">" . h($this->getName()) . " " . 
h($this->strAttribut(ATTR_DEF_FIRST_NAME)) . '[' . $this->get_quick_code() . 
']</h2>';
                return $r;
        }
        function get_all_account()
diff --git a/include/class/fiche_attr.class.php 
b/include/class/fiche_attr.class.php
index 55fdbfb..c424e38 100644
--- a/include/class/fiche_attr.class.php
+++ b/include/class/fiche_attr.class.php
@@ -10,7 +10,7 @@
 require_once NOALYSS_INCLUDE.'/lib/ac_common.php';
 require_once NOALYSS_INCLUDE."/database/attr_def_sql.class.php";
 
-class Fiche_Attr extends Attr_def_SQL
+class Fiche_Attr extends Attr_Def_SQL
 {
     /* example private 
$variable=array("easy_name"=>column_name,"email"=>"column_name_email","val3"=>0);
 */
 
@@ -116,8 +116,8 @@ class Fiche_Attr extends Attr_def_SQL
         $sql="delete from attr_def where ad_id=$1";
         $res=$this->cn->exec_sql($sql, array($this->ad_id));
     }
-
-    /* !
+ 
+   /* !
      * @brief used with a usort function, to sort an array of Attribut on the 
attribut_id (ad_id)
      */
 
@@ -150,11 +150,8 @@ class Fiche_Attr extends Attr_def_SQL
         {
             $nkey=$prefix.$value;
             $array[$nkey]=$this->$value;
-//            $nkey=$prefix.$key;
-//            $array[$nkey]=$this->$value;
 //            
         }
-        tracedebug("card_attribute.log",$array);
         return $array;
     }
 }
diff --git a/include/class/fiche_def.class.php 
b/include/class/fiche_def.class.php
index e92d3a2..feefd25 100644
--- a/include/class/fiche_def.class.php
+++ b/include/class/fiche_def.class.php
@@ -41,6 +41,8 @@ class Fiche_Def
     {
         $this->cn=$p_cn;
         $this->id=$p_id;
+        
+        
     }
     /*!\brief show the content of the form to create  a new Fiche_Def_Ref
     */
@@ -65,7 +67,7 @@ class Fiche_Def
     /*!
      *  \brief  Get attribut of a fiche_def
      *
-     * \return string value of the attribute
+     * \return array of Card_Property 
      */
     function getAttribut()
     {
@@ -80,9 +82,10 @@ class Fiche_Def
         for ($i=0;$i < $Max;$i++)
         {
             $row=Database::fetch_array($Ret,$i);
-            $t = new Fiche_Attr($this->cn);
+            $t = new Card_Property($this->cn);
             $t->ad_id=$row['ad_id'];
             $t->ad_text=$row['ad_text'];
+            $t->av_text="";
             $t->jnt_order=$row['jnt_order'];
             $t->ad_size=$row['ad_size'];
             $t->ad_type=$row['ad_type'];
@@ -189,6 +192,7 @@ $order
      *              nom_mod
      *              class_base
      *              fd_description
+     *              create
      */
     function Add($array)
     {
diff --git a/include/constant.php b/include/constant.php
index fccfb18..179779e 100644
--- a/include/constant.php
+++ b/include/constant.php
@@ -596,7 +596,8 @@ function noalyss_class_autoloader($class) {
         "noalyss\mobile"=>"class/mobile.class.php",
         "profile_mobile_sql"=>"database/profile_mobile_sql.class.php",
         "mobile_device_mtable"=>"class/mobile_device_mtable.class.php",
-        "html_input_noalyss"=>"class/html_input_noalyss.class.php"
+        "html_input_noalyss"=>"class/html_input_noalyss.class.php",
+        "card_property"=>"class/card_property.class.php"
     );
     if ( isset ($aClass[$class]) ) {
         require_once NOALYSS_INCLUDE."/".$aClass[$class];
diff --git a/unit-test/global.example.php b/unit-test/global.example.php
index 69e2a51..3005127 100644
--- a/unit-test/global.example.php
+++ b/unit-test/global.example.php
@@ -19,16 +19,26 @@
 */
 
 // Copyright Author Dany De Bontridder danydb@aevalys.eu
-
+O
 /*
  * Global variables
  */
 global $g_connection,$g_parameter,$g_user;
-define ("DOSSIER",48);
+if (!defined("DOSSIER"))define ("DOSSIER",25);
+
 $_REQUEST['gDossier'] = DOSSIER;
 $g_connection=new Database(DOSSIER);
 $g_parameter = new Noalyss_Parameter_Folder($g_connection);
-$_SESSION[SESSION_KEY.'g_user']='phpcompta';
-$_SESSION[SESSION_KEY.'g_pass']='dany';
+$_SESSION[SESSION_KEY.'use_name']='unit test';
+$_SESSION[SESSION_KEY.'use_first_name']='automatic';
+$_SESSION[SESSION_KEY.'g_user']='admin';
+$_SESSION[SESSION_KEY.'g_pass']='phpcompta';
 $_SESSION[SESSION_KEY.'g_pagesize']='50';
+$_SESSION[SESSION_KEY.'csv_fieldsep']='0';
+$_SESSION[SESSION_KEY.'csv_decimal']='1';
+$_SESSION[SESSION_KEY.'csv_encoding']='utf8';
+$_SESSION[SESSION_KEY.'access_mode']='PC';
 $g_user=new User($g_connection);
+$_ENV['TMP']="/tmp/";
+
+require_once __DIR__.'/facility.class.php';
diff --git a/unit-test/global.php b/unit-test/global.php
index 2b956ab..309b574 100644
--- a/unit-test/global.php
+++ b/unit-test/global.php
@@ -39,5 +39,6 @@ $_SESSION[SESSION_KEY.'csv_decimal']='1';
 $_SESSION[SESSION_KEY.'csv_encoding']='utf8';
 $_SESSION[SESSION_KEY.'access_mode']='PC';
 $g_user=new User($g_connection);
+$_ENV['TMP']="/tmp/";
 
 require_once __DIR__.'/facility.class.php';
diff --git a/unit-test/include/class/acc_ledger_purchase.Test.php 
b/unit-test/include/class/acc_ledger_purchase.Test.php
index cab33bf..e3d6018 100644
--- a/unit-test/include/class/acc_ledger_purchase.Test.php
+++ b/unit-test/include/class/acc_ledger_purchase.Test.php
@@ -245,8 +245,8 @@ class Acc_Ledger_PurchaseTest extends TestCase
         //-- modify card 29 : ELECTR
         $fiche=new Fiche($g_connection,29);
         $fiche->set_f_enable("1");
+        $fiche->setAttribut(20,"33.33");
         $a_attribut=$fiche->to_array();
-        $a_attribut['av_text20']= "33.33";
         $this->assertEquals($a_attribut['av_text20'],33.33,"Attribut 20 set to 
33%");
         
         $fiche->update($a_attribut);
@@ -277,7 +277,7 @@ class Acc_Ledger_PurchaseTest extends TestCase
 
         $this->clean_operation($array['mt']);
         // end test clean
-      //  $fiche_def->RemoveAttribut([20,21,22,51,52,53]);
+        $fiche_def->RemoveAttribut([20,21,22,51,52,53]);
         
     }
 
diff --git a/unit-test/include/class/acc_ledger_sale.Test.php 
b/unit-test/include/class/acc_ledger_sale.Test.php
index 92771de..e932e10 100644
--- a/unit-test/include/class/acc_ledger_sale.Test.php
+++ b/unit-test/include/class/acc_ledger_sale.Test.php
@@ -189,7 +189,7 @@ class Acc_Ledger_SaleTest extends TestCase
 
     /**
      * @covers Acc_Ledger_Sale::input
-     * @todo   Implement testInput().
+     * 
      */
     public function testInput()
     {
@@ -198,7 +198,7 @@ class Acc_Ledger_SaleTest extends TestCase
         $object=new Acc_Ledger_Sale($g_connection, 2);
         
         $info=$object->input($this->array);
-       // var_dump($info);
+        // var_dump($info);
         if (!is_string($info))
         {
             $this->assertTrue(FALSE);
@@ -217,7 +217,6 @@ class Acc_Ledger_SaleTest extends TestCase
 
     /**
      * @covers Acc_Ledger_Sale::heading_detail_sale
-     * @todo   Implement testHeading_detail_sale().
      */
     public function testHeading_detail_sale()
     {
diff --git a/unit-test/include/class/card_property.Test.php 
b/unit-test/include/class/card_property.Test.php
new file mode 100644
index 0000000..61a0610
--- /dev/null
+++ b/unit-test/include/class/card_property.Test.php
@@ -0,0 +1,186 @@
+<?php
+
+use PHPUnit\Framework\TestCase;
+
+/*
+ *   This file is part of NOALYSS.
+ *
+ *   PhpCompta is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   PhpCompta is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with PhpCompta; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+// Copyright (2002-2021) Author Dany De Bontridder <danydb@noalyss.eu>
+
+/**
+ * @file
+ * @brief test the Card Properties 
+ */
+
+/**
+ * @testdox Test card : save attributes, insert card, create card category 
+ * @covers Card_Property 
+ * @backupGlobals enabled
+ */
+class Card_PropertyTest extends TestCase
+{
+
+    const FICHE_DEF='TESTPROPERTY';
+    const FICHE_QCODE='PHPUNIT_CARD';
+
+    /**
+     * @brief 
+     * @global type $g_connection
+     */
+    static function setUpBeforeClass()
+    {
+        require_once 'global.php';
+        global $g_connection;
+        $g_connection=Dossier::connect();
+        // clean if exists
+        $fiche_def_id=$g_connection->get_value("select fd_id from fiche_def 
where fd_label=$1", [self::FICHE_DEF]);
+        if ($g_connection->size()>0)
+        {
+            $g_connection->exec_sql("delete from jnt_fic_attr where fd_id=$1", 
[$fiche_def_id]);
+
+            $g_connection->exec_sql("delete from fiche_detail where f_id in 
(select f_id from fiche where fd_id=$1)",
+                    [$fiche_def_id]);
+            $g_connection->exec_sql("delete from fiche where fd_id=$1", 
[$fiche_def_id]);
+            $g_connection->exec_sql("delete from fiche_def where fd_id=$1", 
[$fiche_def_id]);
+        }
+
+        // create a category of card, type Charges
+        $fiche_def=new Fiche_Def($g_connection);
+        $aParam=["nom_mod"=>"TESTPROPERTY", "fd_description"=>'PHPUNIT test', 
'class_base'=>'600', 'FICHE_REF'=>3,
+            'create'=>1];
+        $fiche_def->add($aParam);
+
+        // add to this new categorie all the possible attributes
+        $aProperty=$g_connection->get_array("select ad_id from attr_def 
+            where 
+            ad_id not in (            select ad_id from jnt_fic_attr where 
fd_id=$1)", [$fiche_def->id]);
+        foreach ($aProperty as $property)
+        {
+            $fiche_def->InsertAttribut($property['ad_id']);
+        }
+        $fiche=new Fiche($g_connection);
+        $fiche->attribut=$fiche_def->getAttribut();
+        foreach ($fiche->attribut as $row)
+        {
+            $fiche->setAttribut($row->ad_id, "av_text = {$row->ad_id}");
+        }
+
+        $fiche->setAttribut(ATTR_DEF_QUICKCODE, self::FICHE_QCODE);
+        $fiche->setAttribut(ATTR_DEF_ACCOUNT, '600');
+        $fiche->setAttribut(ATTR_DEF_TVA, '');
+        $fiche->insert($fiche_def->id, $fiche->to_array());
+        $fiche->load();
+    }
+
+    public static function tearDownAfterClass()
+    {
+        return;
+        require_once 'global.php';
+        global $g_connection;
+        $g_connection=Dossier::connect();
+        // clean if exists
+        $fiche_def_id=$g_connection->get_value("select fd_id from fiche_def 
where fd_label=$1", [self::FICHE_DEF]);
+        if ($g_connection->size()>0)
+        {
+            $g_connection->exec_sql("delete from jnt_fic_attr where fd_id=$1", 
[$fiche_def_id]);
+
+            $g_connection->exec_sql("delete from fiche_detail where f_id in 
(select f_id from fiche where fd_id=$1)",
+                    [$fiche_def_id]);
+            $g_connection->exec_sql("delete from fiche where fd_id=$1", 
[$fiche_def_id]);
+            $g_connection->exec_sql("delete from fiche_def where fd_id=$1", 
[$fiche_def_id]);
+        }
+    }
+
+    public function getFicheDef()
+    {
+        require_once 'global.php';
+
+        global $g_connection;
+        $g_connection=Dossier::connect();
+        $fiche_def_id=$g_connection->get_value("select fd_id from fiche_def 
where fd_label=$1", [self::FICHE_DEF]);
+        $this->assertEquals($g_connection->size(), 1, 'find fiche_def.fd_id');
+        $fiche_def=new Fiche_Def($g_connection, $fiche_def_id);
+        return $fiche_def;
+    }
+
+    public function getFiche()
+    {
+        require_once 'global.php';
+
+        global $g_connection;
+        $g_connection=Dossier::connect();
+        $fiche_id=$g_connection->get_value("select f_id from fiche_detail 
where ad_id=23 and ad_value=$1 ",
+                [self::FICHE_QCODE]);
+        $this->assertEquals($g_connection->size(), 1, 'find fiche.f_id');
+        $fiche=new Fiche($g_connection, $fiche_id);
+        return $fiche;
+    }
+
+    public function testPrint()
+    {
+        $fiche=$this->getFiche();
+        foreach ($fiche->attribut as $row)
+        {
+            $result=$row->print();
+            $this->assertStringStartsWith("<TR>", $result, "does not start 
with TR");
+            $this->assertStringEndsWith("</TR>", $result, "does not end with 
TR");
+        }
+    }
+
+    /**
+     *  @testdox Update with an existing one
+     */
+    public function testUpdate()
+    {
+        $fiche_def=$this->getFiche();
+        $fiche=$this->getFiche();
+        $fiche->load($fiche);
+        $name="test ".microtime();
+        $this->assertFalse($fiche->getAttribut(1)==$name, 'name different');
+        $fiche->setAttribut(1, $name);
+        $aProperty=$fiche->to_array();
+        $this->assertEquals($name, $aProperty['av_text1'], 'name identical in 
array');
+
+        Card_Property::update($fiche);
+
+        Card_Property::load($fiche);
+        $this->assertEquals(trim($name), trim($fiche->strAttribut(1)), 'name 
identical in DB');
+        $this->assertEquals(trim($name), trim($fiche->getName()), 'name 
identical in DB');
+    }
+
+    public function testInput()
+    {
+        $fiche=$this->getFiche();
+        foreach ($fiche->attribut as $row)
+        {
+            $result=$row->print();
+            $this->assertStringStartsWith("<TR>", $result, "does not start 
with TR");
+            $this->assertStringEndsWith("</TR>", $result, "does not end with 
TR");
+        }
+    }
+
+    /**
+     * @testdox Test load with a new card and a existing one
+     */
+    public function testLoad()
+    {
+        $fiche=$this->getFiche();
+        Card_Property::load($fiche);
+        $this->assertEquals(count($fiche->attribut), 35, 'there are not 35 
attributes');
+    }
+
+}
diff --git a/unit-test/include/class/databaseTest.class.php 
b/unit-test/include/class/databaseTest.class.php
new file mode 100644
index 0000000..084986f
--- /dev/null
+++ b/unit-test/include/class/databaseTest.class.php
@@ -0,0 +1,41 @@
+<?php
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Generated by PHPUnit_SkeletonGenerator on 2016-02-11 at 15:41:40.
+ */
+class DatabaseTest extends TestCase
+
+{
+
+    /**
+     * @var Database
+     */
+    protected $object;
+
+    /**
+     * Sets up the fixture, for example, opens a network connection.
+     * This method is called before a test is executed.
+     */
+    protected function setUp()
+    {
+        $this->object=new Database(DOSSIER);
+    }
+
+    /**
+     * Tears down the fixture, for example, closes a network connection.
+     * This method is called after a test is executed.
+     */
+    protected function tearDown()
+    {
+        
+    }
+    public function testDatabase()
+    {
+        $this->assertEquals(DBVERSION,$this->object->get_version(),"check 
Version");
+        
+    }
+
+    
+
+}
diff --git a/unit-test/include/class/document.Test.php 
b/unit-test/include/class/document.Test.php
index 84a70cd..cb5b7ba 100644
--- a/unit-test/include/class/document.Test.php
+++ b/unit-test/include/class/document.Test.php
@@ -121,14 +121,14 @@ class DocumentTest extends TestCase
         $this->assertTrue ( $document->replace('CUST_NAME',$array) == 'Client 
1','CUST_NAME');
     }
     /**
-     * 
+     * @testdox Document::generate(), 
Document::parseDocument(),Document::replace(); require  unoconv -l in another 
session
      * @covers Document::generate(), 
Document::parseDocument(),Document::replace();
      */
     function testGenerate()
     {
+        require_once 'global.php';
         $cn=Dossier::connect();
         $md_id=$cn->get_value('select max(md_id) md_id from document_modele 
where md_name=$1',['Balise']);
-        echo " You must start unoconv -l in another session ";
         $array['e_client']='CLIENT';
         $array['e_date']='21.03.2020';
         $document=new Document($cn,$md_id);
@@ -139,13 +139,32 @@ class DocumentTest extends TestCase
         $this->assertEquals($document->d_filename ,'all-tags.odt','Generated 
File ');
         $cnt_after=$cn->get_value("select count(*) from document");
         $this->assertTrue ($cnt_after == $cnt_before+1,"One file generated");
+        
+        
     }
     /**
-     * @depends DocumentTest::testGenerate
-     * @covers Document::export_pdf, Document::transform2pdf()
+     * @testdox test $_ENV['TMP'] 
+     */
+    function testGenerateTmp()
+    {
+          $cn=Dossier::connect();
+        $md_id=$cn->get_value('select max(md_id) md_id from document_modele 
where md_name=$1',['Balise']);
+        $array['e_client']='CLIENT';
+        $array['e_date']='21.03.2020';
+        $document=new Document($cn,$md_id);
+        $document->ag_id=2;
+        $document->md_id=$md_id;
+        $cnt_before=$cn->get_value("select count(*) from document");
+        $_ENV['TMP']='/not.exist';
+        $this->assertContains ('échoué',$document->generate($array));
+    }
+    /**
+     * @testdox export PDF tests Document::export_pdf, 
Document::transform2pdf()
+     * 
      */
-    function testExportPDF()
+    function testExtractPdf()
     {
+        require "global.php";
         $cn=Dossier::connect();
         $d_id=$cn->get_value('select max(d_id) d_id from document');
         $document=new Document($cn);
diff --git a/unit-test/include/class/fiche.Test.php 
b/unit-test/include/class/fiche.Test.php
index 9bb77d8..74054d1 100644
--- a/unit-test/include/class/fiche.Test.php
+++ b/unit-test/include/class/fiche.Test.php
@@ -1,4 +1,5 @@
 <?php
+
 use PHPUnit\Framework\TestCase;
 
 /**
@@ -6,6 +7,7 @@ use PHPUnit\Framework\TestCase;
  * @backupGlobals enabled
  */
 require 'global.php';
+
 class FicheTest extends TestCase
 {
 
@@ -33,15 +35,23 @@ class FicheTest extends TestCase
         
     }
 
+    static function tearDownAfterClass()
+    {
+        include 'global.php';
+        $fiche_def=new Fiche_Def($g_connection, 5);
+        // prepare test , clean 
+        $fiche_def->RemoveAttribut([20, 21, 22, 51, 52, 53]);
+    }
+
     /**
      * @covers Fiche::cmp_name
      */
     public function testCmp_name()
     {
         global $g_connection;
-       $fiche=new \Fiche($g_connection,21);
-       $fiche_2=new \Fiche($g_connection,25);
-       $this->assertGreaterThan(\Fiche::cmp_name($fiche, $fiche_2),0);
+        $fiche=new \Fiche($g_connection, 21);
+        $fiche_2=new \Fiche($g_connection, 25);
+        $this->assertGreaterThan(\Fiche::cmp_name($fiche, $fiche_2), 0);
     }
 
     /**
@@ -50,12 +60,11 @@ class FicheTest extends TestCase
     public function testGet_bk_account()
     {
         include 'global.php';
-     $this->object=new Fiche($g_connection);
-     $result=$this->object->get_bk_account();
-     $this->assertEquals(gettype($result),'array');
-     $count = count($result);
-     $this->assertGreaterThan(0,$count);
-     
+        $this->object=new Fiche($g_connection);
+        $result=$this->object->get_bk_account();
+        $this->assertEquals(gettype($result), 'array');
+        $count=count($result);
+        $this->assertGreaterThan(0, $count);
     }
 
     /**
@@ -64,55 +73,62 @@ class FicheTest extends TestCase
     public function testGet_row()
     {
         include 'global.php';
-        $card_count=$g_connection->get_array("select count(*),f_id ". 
-        " from jrnx ".
-        " where ". 
-        " f_id is not null ".
-        "group by f_id order by count(*) desc");
-        $a=new Fiche($g_connection,$card_count[0]['f_id']);
-       try {
-            $a->get_row(235,238);
-            $this->assertFalse(TRUE,"Exception periode invalide");
-       } catch (\Exception $e) {
-           $this->assertTrue(TRUE);
-       }
-        $a_result= $a->get_row(92,131);
+        $card_count=$g_connection->get_array("select count(*),f_id ".
+                " from jrnx ".
+                " where ".
+                " f_id is not null ".
+                "group by f_id order by count(*) desc");
+        $a=new Fiche($g_connection, $card_count[0]['f_id']);
+        try
+        {
+            $a->get_row(235, 238);
+            $this->assertFalse(TRUE, "Exception periode invalide");
+        }
+        catch (\Exception $e)
+        {
+            $this->assertTrue(TRUE);
+        }
+        $a_result=$a->get_row(92, 131);
         // Size == 25
-        
+
         $nb_result=count($a_result);
-        $this->assertEquals ($nb_result,3,"Size array not correct ");
-        $this->assertEquals(198.74,$a_result[0][13]["deb_montant"],"Debit from 
operation 12");
+        $this->assertEquals($nb_result, 3, "Size array not correct ");
+        $this->assertEquals(198.74, $a_result[0][13]["deb_montant"], "Debit 
from operation 12");
     }
-    
+
     /**
      * @covers Fiche::count_by_modele()
      */
     public function testCount_by_modele()
     {
 
-        $nb=$this->object->count_by_modele(1,"","");
-        $this->assertEquals(4,$nb,"number of Sales Card ");
-        $nb=$this->object->count_by_modele(3,"eau","");
-        $this->assertEquals(1,$nb,"Purchase card water ");
-        $nb=$this->object->count_by_modele(3,"EAU","");
-        $this->assertEquals(1,$nb,"Purchase card water ");
-        $nb=$this->object->count_by_modele(3,"ZZ","");
-        $this->assertEquals(0,$nb,"no  card  found");
-        $nb=$this->object->count_by_modele(3000,"","");
-        $this->assertEquals(0,$nb,"no  card found");
-        $nb=$this->object->count_by_modele(3,"","");
-        $this->assertEquals(7,$nb,"Purchase cards ");
+        $nb=$this->object->count_by_modele(1, "", "");
+        $this->assertEquals(4, $nb, "number of Sales Card ");
+        $nb=$this->object->count_by_modele(3, "eau", "");
+        $this->assertEquals(1, $nb, "Purchase card water ");
+        $nb=$this->object->count_by_modele(3, "EAU", "");
+        $this->assertEquals(1, $nb, "Purchase card water ");
+        $nb=$this->object->count_by_modele(3, "ZZ", "");
+        $this->assertEquals(0, $nb, "no  card  found");
+        $nb=$this->object->count_by_modele(3000, "", "");
+        $this->assertEquals(0, $nb, "no  card found");
+        $nb=$this->object->count_by_modele(3, "", "");
+        $this->assertEquals(7, $nb, "Purchase cards ");
         // attempt to inject SQL command, you must get an error
-        try {
-            $nb=@$this->object->count_by_modele(3,""," ;delete from jrn;");
-            $this->assertFalse(true,"Inject SQL command not found");
-        }  catch(Exception $e) {
-            $this->assertTrue(true,"Inject SQL command found");
+        try
+        {
+            $nb=@$this->object->count_by_modele(3, "", " ;delete from jrn;");
+            $this->assertFalse(true, "Inject SQL command not found");
+        }
+        catch (Exception $e)
+        {
+            $this->assertTrue(true, "Inject SQL command found");
         }
     }
+
     /**
-     * @covers Fiche->Summary
-     * @covers Fiche->get_by_category
+     * @covers Fiche::Summary
+     * @covers Fiche::get_by_category
      * @covers Fiche::GetByDef
      */
     public function testSummary()
@@ -120,9 +136,142 @@ class FicheTest extends TestCase
         $_REQUEST['ac']="CARD";
         $this->object->fiche_def_ref=-1;
         $r=$this->object->summary();
-        $this->assertEquals('',$r);
+        $this->assertEquals('', $r);
         $this->object->fiche_def_ref=9;
         $r=$this->object->summary();
-        $this->assertContains('</TABLE>',$r);
+        $this->assertContains('</TABLE>', $r);
     }
+    /**
+     * 
+     *
+     */
+    function testFicheDefInsertAttribut()
+    {
+        global $g_connection;
+        $fiche_def=new Fiche_Def($g_connection, 5);
+        // prepare test , clean 
+        $fiche_def->RemoveAttribut([20, 21, 22, 51, 52, 53]);
+        $this->assertEquals(35,
+                $g_connection->get_value("select count(*) from fiche_detail 
join fiche using (f_id)
+                where fd_id=5"), "Efface 6 attributs");
+
+        // percent deductible
+        $fiche_def->InsertAttribut(20);
+        $fiche_def->InsertAttribut(21);
+        $fiche_def->InsertAttribut(22);
+
+        // accouting for not deductible
+        $fiche_def->InsertAttribut(51);
+        $fiche_def->InsertAttribut(52);
+        $fiche_def->InsertAttribut(53);
+
+        // check that all card has these attributes
+        $this->assertEquals(77,
+                $g_connection->get_value("select count(*) from fiche_detail 
join fiche using (f_id)
+                where fd_id=5"), "Ajout 6 attributs");
+    }
+
+    /**
+     * @covers Fiche_Def::RemoveAttribut
+     * @depends testFicheDefInsertAttribut
+     */
+    function testFicheDefRemoveAttribut()
+    {
+        global $g_connection;
+        $fiche_def=new Fiche_Def($g_connection, 5);
+
+        // prepare test , clean 
+        $fiche_def->RemoveAttribut([20, 21, 22, 51, 52, 53]);
+
+        $this->assertEquals(35,
+                $g_connection->get_value("select count(*) from fiche_detail 
join fiche using (f_id)
+                where fd_id=5"), "Efface 6 attributs");
+    }
+
+    /**
+     * @testdox update attribute , remove them , Fiche->update and 
Card_Property::update
+     *
+     */
+    public function testUpdateAttribut()
+    {
+        $this->testFicheDefRemoveAttribut();
+        $this->testFicheDefInsertAttribut();
+
+        global $g_connection;
+
+        // modify attribute for card category , add VAT non ded, Tax non ded , 
VAT completely non ded 0%
+        // category Misc Services & goods (5)
+        //-- modify card 29 : ELECTR
+        $fiche=new Fiche($g_connection, 29);
+        $fiche->set_f_enable("1");
+        $fiche->setAttribut(20, "33.33");
+        $a_attribut=$fiche->to_array();
+        $this->assertEquals($a_attribut['av_text20'], 33.33, "Attribut 20 set 
to 33%");
+
+        $fiche->update($a_attribut);
+
+        $this->assertEquals("33.33",
+                $g_connection->get_value("select ad_value from fiche_detail 
where f_id=$1 and ad_id=$2", [29, 20]),
+                "Attribut ad_id 20 inserted");
+
+        $this->assertEquals("33.33", $fiche->strAttribut(20), "retrieve 
attribute 20");
+        $fiche->setAttribut(20, "0.05");
+        Card_Property::update($fiche);
+         $this->assertEquals("0.05",
+                $g_connection->get_value("select ad_value from fiche_detail 
where f_id=$1 and ad_id=$2", [29, 20]),
+                "Attribut ad_id 20 updated");
+    }
+
+    function testInexistantCard()
+    {
+        global $g_connection;
+        $last_card=$g_connection->get_next_seq('s_fiche');
+        $last_card=$g_connection->get_next_seq('s_fiche');
+        $inexistant_fiche=new Fiche($g_connection, $last_card+2000);
+        $_POST['av_text1']='not exist';
+        try
+        {
+            $inexistant_fiche->update();
+            $this->assertTrue(false, 'Inexistant card not detected');
+        }
+        catch (Exception $e)
+        {
+            $this->assertTrue(true, "Inexistant card properly detected");
+        }
+    }
+
+    /**
+     * @covers Fiche::get_by_qcode
+     */
+    public function testQueryAttribute()
+    {
+        global $g_connection;
+
+        $fiche_goods=new Fiche($g_connection);
+        $fiche_goods->get_by_qcode("MARCHA");
+        $this->assertEquals($fiche_goods->id, 23, "retrieve card by qcode");
+        $this->assertEquals($fiche_goods->strAttribut(ATTR_DEF_NAME), 
"Marchandise1", "Retrieve name");
+        $this->assertEquals($fiche_goods->getName(), "Marchandise1", "Retrieve 
name from db");
+    }
+
+    /**
+     * @covers Fiche::insert
+     */
+    public function testInsertAndRemoveCard()
+    {
+        global $g_connection;
+        $last_card=$g_connection->get_next_seq('s_fiche');
+        $nb_fiche=$g_connection->get_value("select count(*) from fiche");
+        $new_fiche=new Fiche($g_connection);
+        $aProperty=array('av_text1'=>'Nom', 'av_text23'=>'ZZZTEST');
+        $new_fiche->insert(5, $aProperty);
+        $this->assertGreaterThan($last_card, $new_fiche->id, 'card created');
+        $nb_fiche_after=$g_connection->get_value("select count(*) from fiche");
+        $this->assertGreaterThan($nb_fiche, $nb_fiche_after, 'card created');
+        $this->assertEquals("1", $new_fiche->get_f_enable(), "By default 
enable");
+        $new_fiche->remove();
+        $nb_fiche_after=$g_connection->get_value("select count(*) from fiche");
+        $this->assertEquals($nb_fiche, $nb_fiche_after, 'card removed');
+    }
+
 }
diff --git a/unit-test/include/lib/table_sql_sqlTest.class.php 
b/unit-test/include/lib/table_sql.Test.php
similarity index 100%
rename from unit-test/include/lib/table_sql_sqlTest.class.php
rename to unit-test/include/lib/table_sql.Test.php
diff --git a/unit-test/test-file.sh b/unit-test/test-file.sh
index a312089..c54bee7 100755
--- a/unit-test/test-file.sh
+++ b/unit-test/test-file.sh
@@ -2,7 +2,9 @@
 help(){ 
        echo "$0 -f filename [-i function ]"
        echo "     -f filename , file to test"
+       echo "     -d folder to test "
        echo "     -i function , optional filter to a function"
+       echo "     -c in the folder coverage , show the coverage"
 }
 
 cd `dirname $0`
@@ -11,26 +13,46 @@ CUR_DIR=`pwd`
 PHPUNIT=$CUR_DIR/phpunit
 FILETOTEST=""
 FUNCTION=""
-while getopts "f:i:" opt; do
+COVERAGE=""
+FOLDERTEST=""
+
+while getopts "f:i:cd:" opt; do
        case $opt in
+               d)
+                       FOLDERTEST=$OPTARG
+                       ;;
                f)
                        FILETOTEST=$OPTARG
                        ;;
                i)
                        FUNCTION=$OPTARG
                        ;;
+               c)      COVERAGE="--whitelist=../include 
--coverage-html=coverage"
+                       ;;
                *)
                        help
                        exit 1
        esac
 done
 
-if [ -z "$FILETOTEST" ] ;then
+if [ -z "$FILETOTEST" -a -z "$FOLDERTEST" ] ;then
        help
-       echo "-f is mandatory"
+       echo "please give a file or a folder to test "
        exit 2
 fi     
 
+if [ ! -z "$FOLDERTEST" -a ! -d "$FOLDERTEST" ] ; then
+       echo "Folder doesn't exist"
+       exit 3
+fi
+
+
+if [ ! -z "$FOLDERTEST" -a  -d "$FOLDERTEST" ] ; then
+
+       $PHPUNIT --bootstrap bootstrap.php $COVERAGE --testdox --color 
$FOLDERTEST
+       exit $?
+fi
+
 if [ ! -f "$FILETOTEST" ] ; then
        echo "File $FILETOTEST not found"
        exit 1
@@ -43,6 +65,7 @@ else
 
        # $PHPUNIT --bootstrap bootstrap.php --whitelist $FILETOTEST 
--coverage-text=${FILETOTEST%.php}.txt  --color $FILETOTEST 
        # $PHPUNIT --bootstrap $CUR_DIR/bootstrap.php 
--whitelist=$CUR_DIR/../include/class --coverage-html=coverage --color 
$FILETOTEST 
-       $PHPUNIT --bootstrap bootstrap.php --whitelist=../include 
--coverage-html=coverage $FILETOTEST --testdox-html 
${FILETOTEST%.php}-testdox.html --color $FILETOTEST 
+       $PHPUNIT --bootstrap bootstrap.php $COVERAGE $FILETOTEST --testdox 
--color $FILETOTEST 
+       #$PHPUNIT --bootstrap bootstrap.php --whitelist=../include 
--coverage-html=coverage $FILETOTEST --testdox-html 
${FILETOTEST%.php}-testdox.html --color $FILETOTEST 
 fi
 



reply via email to

[Prev in Thread] Current Thread [Next in Thread]