Dolibarr / dolibarr

Dolibarr ERP CRM is a modern software package to manage your company or foundation's activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). it's an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers.
https://www.dolibarr.org
GNU General Public License v3.0
5.46k stars 2.79k forks source link

Certain fields aren't propagating to new invoice on POST to API #26875

Open komali2 opened 11 months ago

komali2 commented 11 months ago

Bug

If using the API to create a new invoice via POST to /api, certain fields won't be saved, such as total_ttc , total_ht, and ref. These fields WILL however correctly be saved on PUT to /api/{id}.

I'm not too familiar with the Dolibarr codebase, but based on: https://github.com/Dolibarr/dolibarr/blob/91e6028eb253d931a2e9b9c98cee9ccf0f349c72/htdocs/compta/facture/class/api_invoices.class.php#L296

/**
     * Create invoice object
     *
     * @param array $request_data   Request datas
     * @return int                  ID of invoice
     */
    public function post($request_data = null)
    {
        if (!DolibarrApiAccess::$user->rights->facture->creer) {
            throw new RestException(401, "Insuffisant rights");
        }
        // Check mandatory fields
        $result = $this->_validate($request_data);

        foreach ($request_data as $field => $value) {
            $this->invoice->$field = $value;
        }
        if (!array_key_exists('date', $request_data)) {
            $this->invoice->date = dol_now();
        }
        /* We keep lines as an array
         if (isset($request_data["lines"])) {
            $lines = array();
            foreach ($request_data["lines"] as $line) {
                array_push($lines, (object) $line);
            }
            $this->invoice->lines = $lines;
        }*/

        if ($this->invoice->create(DolibarrApiAccess::$user, 0, (empty($request_data["date_lim_reglement"]) ? 0 : $request_data["date_lim_reglement"])) < 0) {
            throw new RestException(500, "Error creating invoice", array_merge(array($this->invoice->error), $this->invoice->errors));
        }
        return $this->invoice->id;
    }

It doesn't look to me like there's any kind of filtering. The fields should just propagate, shouldn't they?

To rule out permissions issues, I've granted this API user all permissions.

Environment Version

18.0.2

Environment OS

Linux 6dea47b70f3f 5.15.0-89-generic #99-Ubuntu SMP Mon Oct 30 20:42:41 UTC 2023 x86_64

Environment Web server

Apache/2.4.57 (Unix)

Environment PHP

8.2.10

Environment Database

MySQL or MariaDB 11.2.2-MariaDB-1:11.2.2+maria~ubu2204

Environment URL(s)

api/index.php/explorer/#/invoices

Expected and actual behavior

Example CURL:

curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' --header 'DOLAPIKEY: KEY' -d '{ \ 
   "module": null, \ 
   "id": "898", \ 
   "entity": "1", \ 
   "import_key": null, \ 
   "array_options": [], \ 
   "array_languages": null, \ 
   "contacts_ids": [], \ 
   "linked_objects": [], \ 
   "linkedObjectsIds": [], \ 
   "oldref": null, \ 
   "fk_project": null, \ 
   "contact_id": null, \ 
   "user": null, \ 
   "origin": null, \ 
   "origin_id": null, \ 
   "ref": "SUPER TEST SUPER TEST", \ 
   "ref_ext": "VQ58317546", \ 
   "statut": "0", \ 
   "status": "0", \ 
   "country_id": null, \ 
   "country_code": null, \ 
   "state_id": null, \ 
   "region_id": null, \ 
   "mode_reglement_id": "4", \ 
   "cond_reglement_id": "0", \ 
   "model_pdf": "sponge", \ 
   "last_main_doc": null, \ 
   "fk_bank": null, \ 
   "fk_account": "3", \ 
   "note_public": null, \ 
   "note_private": null, \ 
   "total_ht": "0.00000000", \ 
   "total_tva": "0.00000000", \ 
   "total_localtax1": "0.00000000", \ 
   "total_localtax2": "0.00000000", \ 
   "total_ttc": "222.00000000", \ 
   "lines": [], \ 
   "name": null, \ 
   "lastname": null, \ 
   "firstname": null, \ 
   "civility_id": null, \ 
   "date_creation": 1701095929, \ 
   "date_validation": "", \ 
   "date_modification": 1701096381, \ 
   "date_update": null, \ 
   "date_cloture": null, \ 
   "user_author": "5", \ 
   "user_creation": null, \ 
   "user_creation_id": null, \ 
   "user_valid": null, \ 
   "user_validation": null, \ 
   "user_validation_id": null, \ 
   "user_closing_id": null, \ 
   "user_modification": null, \ 
   "user_modification_id": null, \ 
   "specimen": 0, \ 
   "labelStatus": null, \ 
   "showphoto_on_popup": null, \ 
   "nb": [], \ 
   "output": null, \ 
   "extraparams": [], \ 
   "type": "0", \ 
   "subtype": null, \ 
   "totalpaid": 0, \ 
   "totaldeposits": null, \ 
   "totalcreditnotes": null, \ 
   "sumpayed": null, \ 
   "sumpayed_multicurrency": null, \ 
   "sumdeposit": null, \ 
   "sumdeposit_multicurrency": null, \ 
   "sumcreditnote": null, \ 
   "sumcreditnote_multicurrency": null, \ 
   "remaintopay": "222", \ 
   "fk_incoterms": "0", \ 
   "label_incoterms": null, \ 
   "location_incoterms": "", \ 
   "brouillon": 1, \ 
   "socid": "14", \ 
   "fk_user_author": "5", \ 
   "fk_user_valid": null, \ 
   "fk_user_modif": null, \ 
   "date": 1701043200, \ 
   "datem": 1701096381, \ 
   "date_livraison": null, \ 
   "delivery_date": null, \ 
   "ref_client": null, \ 
   "ref_customer": null, \ 
   "remise_absolue": null, \ 
   "remise_percent": null, \ 
   "revenuestamp": "0.00000000", \ 
   "resteapayer": null, \ 
   "close_code": null, \ 
   "close_note": null, \ 
   "paye": "0", \ 
   "module_source": null, \ 
   "pos_source": null, \ 
   "fk_fac_rec_source": null, \ 
   "fk_facture_source": null, \ 
   "date_lim_reglement": 1701043200, \ 
   "cond_reglement_code": null, \ 
   "cond_reglement_doc": null, \ 
   "mode_reglement_code": "LIQ", \ 
   "line": null, \ 
   "fac_rec": null, \ 
   "date_pointoftax": "", \ 
   "fk_multicurrency": "0", \ 
   "multicurrency_total_ht": "0.00000000", \ 
   "multicurrency_total_tva": "0.00000000", \ 
   "multicurrency_total_ttc": "777.00000000", \ 
   "situation_cycle_ref": null, \ 
   "situation_counter": null, \ 
   "situation_final": "0", \ 
   "tab_previous_situation_invoice": [], \ 
   "tab_next_situation_invoice": [], \ 
   "retained_warranty": "0", \ 
   "retained_warranty_date_limit": "", \ 
   "retained_warranty_fk_cond_reglement": "0", \ 
   "trackid": "inv898" \ 
 } \ 
 ' 'https://example.com/invoices'

Response: 901

Now GET the same Invoice (901)

curl -X GET --header 'Accept: application/json' --header 'DOLAPIKEY: key' 'example.com/invoices/901?contact_list=1'

Response:

{
  "module": null,
  "id": "901",
  "entity": "1",
  "import_key": null,
  "array_options": [],
  "array_languages": null,
  "contacts_ids": [],
  "linked_objects": [],
  "linkedObjectsIds": [],
  "oldref": null,
  "fk_project": null,
  "contact_id": null,
  "user": null,
  "origin": null,
  "origin_id": null,
  "ref": "(PROV901)",
  "ref_ext": "VQ58317546",
  "statut": "0",
  "status": "0",
  "country_id": null,
  "country_code": null,
  "state_id": null,
  "region_id": null,
  "mode_reglement_id": "4",
  "cond_reglement_id": "0",
  "demand_reason_id": null,
  "transport_mode_id": null,
  "shipping_method_id": null,
  "shipping_method": null,
  "multicurrency_code": "TWD",
  "multicurrency_tx": "1.00000000",
  "model_pdf": "sponge",
  "last_main_doc": null,
  "fk_bank": null,
  "fk_account": "3",
  "note_public": null,
  "note_private": null,
  "total_ht": "0.00000000",
  "total_tva": "0.00000000",
  "total_localtax1": "0.00000000",
  "total_localtax2": "0.00000000",
  "total_ttc": "0.00000000",
  "lines": [],
  "name": null,
  "lastname": null,
  "firstname": null,
  "civility_id": null,
  "date_creation": 1701156247,
  "date_validation": "",
  "date_modification": 1701156247,
  "date_update": null,
  "date_cloture": null,
  "user_author": "5",
  "user_creation": null,
  "user_creation_id": null,
  "user_valid": null,
  "user_validation": null,
  "user_validation_id": null,
  "user_closing_id": null,
  "user_modification": null,
  "user_modification_id": null,
  "specimen": 0,
  "labelStatus": null,
  "showphoto_on_popup": null,
  "nb": [],
  "output": null,
  "extraparams": [],
  "type": "0",
  "subtype": null,
  "totalpaid": 0,
  "totaldeposits": null,
  "totalcreditnotes": null,
  "sumpayed": null,
  "sumpayed_multicurrency": null,
  "sumdeposit": null,
  "sumdeposit_multicurrency": null,
  "sumcreditnote": null,
  "sumcreditnote_multicurrency": null,
  "remaintopay": "0",
  "fk_incoterms": "0",
  "label_incoterms": null,
  "location_incoterms": "",
  "brouillon": 1,
  "socid": "14",
  "fk_user_author": "5",
  "fk_user_valid": null,
  "fk_user_modif": null,
  "date": 1701043200,
  "datem": 1701156247,
  "date_livraison": null,
  "delivery_date": null,
  "ref_client": null,
  "ref_customer": null,
  "remise_absolue": null,
  "remise_percent": null,
  "revenuestamp": "0.00000000",
  "resteapayer": null,
  "close_code": null,
  "close_note": null,
  "paye": "0",
  "module_source": null,
  "pos_source": null,
  "fk_fac_rec_source": null,
  "fk_facture_source": null,
  "date_lim_reglement": 1701043200,
  "cond_reglement_code": null,
  "cond_reglement_doc": null,
  "mode_reglement_code": "LIQ",
  "line": null,
  "fac_rec": null,
  "date_pointoftax": "",
  "fk_multicurrency": "0",
  "multicurrency_total_ht": "0.00000000",
  "multicurrency_total_tva": "0.00000000",
  "multicurrency_total_ttc": "0.00000000",
  "situation_cycle_ref": null,
  "situation_counter": null,
  "situation_final": "0",
  "tab_previous_situation_invoice": [],
  "tab_next_situation_invoice": [],
  "retained_warranty": "0",
  "retained_warranty_date_limit": "",
  "retained_warranty_fk_cond_reglement": "0"
}

Steps to reproduce the behavior

No response

Attached files

No response

komali2 commented 7 months ago

I switched on debug logging, and this shows that the SQL query is setting the price values as 0

2024-03-19 12:38:22 DEBUG 123.193.242.33 sql=INSERT INTO llx_facturedet (fk_facture, fk_parent_line, label, description, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, fk_product, product_type, remise_percent, subprice, ref_ext, fk_remise_except, date_start, date_end, fk_code_ventilation, rang, special_code, fk_product_fournisseur_price, buy_price_ht, info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2, situation_percent, fk_prev_id, fk_unit, fk_user_author, fk_user_modif, fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc) VALUES (906, null, null, 'Original Chicken Biscuit $340.0', 1, '', 0, 0, 0, '0', '0', 1, 0, 0, 0, '', null, '2023-11-27 04:15:23', '2023-11-27 04:15:23', 0, 0, 0, null, 0, '0', 0, 0, 0, 0, 0, 100, null, NULL, 4, 4, 0, 'TWD', 0, 0, 0, 0)

komali2 commented 7 months ago

I think this is the whole debug log for one transaction

2024-03-19 12:38:22 NOTICE     sql=SELECT transkey, transvalue FROM llx_overwrite_trans where (lang='en_US' OR lang IS NULL) AND entity IN (0, 0,1) ORDER BY lang DESC
2024-03-19 12:38:22 INFO       Load a dedicated API file moduleobject=invoices moduledirforclass=compta/facture
2024-03-19 12:38:22 INFO       Search api file /compta/facture/class/api_invoices.class.php => dir_part_file=/var/www/html/compta/facture/class/api_invoices.class.php classname=Invoices
2024-03-19 12:38:22 DEBUG      sql=SELECT u.login, u.datec, u.api_key, u.tms as date_modification, u.entity FROM llx_user as u WHERE u.api_key = 'KEY' OR u.api_key = ''
2024-03-19 12:38:22 DEBUG      sql=SELECT u.rowid, u.lastname, u.firstname, u.employee, u.gender, u.civility as civility_code, u.birth, u.email, u.personal_email, u.job, u.socialnetworks, u.signature, u.office_phone, u.office_fax, u.user_mobile, u.personal_mobile, u.address, u.zip, u.town, u.fk_state as state_id, u.fk_country as country_id, u.admin, u.login, u.note_private, u.note_public, u.pass, u.pass_crypted, u.pass_temp, u.api_key, u.fk_soc, u.fk_socpeople, u.fk_member, u.fk_user, u.ldap_sid, u.fk_user_expense_validator, u.fk_user_holiday_validator, u.statut as status, u.lang, u.entity, u.datec as datec, u.tms as datem, u.datelastlogin as datel, u.datepreviouslogin as datep, u.flagdelsessionsbefore, u.iplastlogin, u.ippreviouslogin, u.datelastpassvalidation, u.datestartvalidity, u.dateendvalidity, u.photo as photo, u.openid as openid, u.accountancy_code, u.thm, u.tjm, u.salary, u.salaryextra, u.weeklyhours, u.color, u.dateemployment, u.dateemploymentend, u.fk_warehouse, u.ref_ext, u.default_range, u.default_c_exp_tax_cat, u.national_registration_number, u.ref_employee, c.code as country_code, c.label as country, d.code_departement as state_code, d.nom as state FROM llx_user as u LEFT JOIN llx_c_country as c ON u.fk_country = c.rowid LEFT JOIN llx_c_departements as d ON u.fk_state = d.rowid WHERE u.entity IN (0, 1) AND u.login = 'caleb' ORDER BY u.entity ASC
2024-03-19 12:38:22 DEBUG      sql=SELECT rowid, name, label, type, size, elementtype, fieldunique, fieldrequired, param, pos, alwayseditable, perms, langs, list, printable, totalizable, fielddefault, fieldcomputed, entity, enabled, help, css, cssview, csslist FROM llx_extrafields WHERE elementtype = 'user' ORDER BY pos
2024-03-19 12:38:22 DEBUG      sql=SELECT DISTINCT r.module, r.perms, r.subperms FROM llx_user_rights as ur, llx_rights_def as r WHERE r.id = ur.fk_id AND r.entity = 1 AND ur.entity = 1 AND ur.fk_user= 4 AND r.perms IS NOT NULL AND r.perms NOT LIKE '%_advance'
2024-03-19 12:38:22 DEBUG      sql=SELECT DISTINCT r.module, r.perms, r.subperms FROM llx_usergroup_rights as gr, llx_usergroup_user as gu, llx_rights_def as r WHERE r.id = gr.fk_id AND gr.entity = 1 AND gu.entity IN (0,1) AND r.entity = 1 AND gr.fk_usergroup = gu.fk_usergroup AND gu.fk_user = 4 AND r.perms IS NOT NULL
2024-03-19 12:38:22 INFO       Facture::create user=4 date=1701058523
2024-03-19 12:38:22 DEBUG      sql=SELECT s.rowid, s.nom as name, s.name_alias, s.entity, s.ref_ext, s.address, s.datec as date_creation, s.prefix_comm, s.status, s.fk_warehouse, s.price_level, s.tms as date_modification, s.fk_user_creat, s.fk_user_modif, s.phone, s.fax, s.email, s.socialnetworks, s.url, s.zip, s.town, s.note_private, s.note_public, s.client, s.fournisseur, s.siren as idprof1, s.siret as idprof2, s.ape as idprof3, s.idprof4, s.idprof5, s.idprof6, s.capital, s.tva_intra, s.fk_typent as typent_id, s.fk_effectif as effectif_id, s.fk_forme_juridique as forme_juridique_code, s.webservices_url, s.webservices_key, s.model_pdf, s.last_main_doc, s.code_compta, s.code_compta_fournisseur, s.accountancy_code_buy, s.accountancy_code_sell, s.vat_reverse_charge as soc_vat_reverse_charge, s.code_client, s.code_fournisseur, s.parent, s.barcode, s.fk_departement as state_id, s.fk_pays as country_id, s.fk_stcomm, s.mode_reglement, s.cond_reglement, s.deposit_percent, s.transport_mode, s.fk_account, s.tva_assuj, s.mode_reglement_supplier, s.cond_reglement_supplier, s.transport_mode_supplier, s.localtax1_assuj, s.localtax1_value, s.localtax2_assuj, s.localtax2_value, s.fk_prospectlevel, s.default_lang, s.logo, s.logo_squarred, s.fk_shipping_method, s.outstanding_limit, s.import_key, s.canvas, s.fk_incoterms, s.location_incoterms, s.order_min_amount, s.supplier_order_min_amount, s.fk_multicurrency, s.multicurrency_code, fj.libelle as forme_juridique, e.libelle as effectif, c.code as country_code, c.label as country, d.code_departement as state_code, d.nom as state, r.rowid as region_id, r.code_region as region_code, st.libelle as stcomm, st.picto as stcomm_picto, te.code as typent_code, i.libelle as label_incoterms, s.remise_client, s.remise_supplier FROM llx_societe as s LEFT JOIN llx_c_effectif as e ON s.fk_effectif = e.id LEFT JOIN llx_c_country as c ON s.fk_pays = c.rowid LEFT JOIN llx_c_stcomm as st ON s.fk_stcomm = st.id LEFT JOIN llx_c_forme_juridique as fj ON s.fk_forme_juridique = fj.code LEFT JOIN llx_c_departements as d ON s.fk_departement = d.rowid LEFT JOIN llx_c_regions as r ON d.fk_region = r.code_region  LEFT JOIN llx_c_typent as te ON s.fk_typent = te.id LEFT JOIN llx_c_incoterms as i ON s.fk_incoterms = i.rowid WHERE s.entity IN (1) AND s.rowid = 14
2024-03-19 12:38:22 DEBUG      sql=SELECT rowid, name, label, type, size, elementtype, fieldunique, fieldrequired, param, pos, alwayseditable, perms, langs, list, printable, totalizable, fielddefault, fieldcomputed, entity, enabled, help, css, cssview, csslist FROM llx_extrafields WHERE elementtype = 'societe' ORDER BY pos
2024-03-19 12:38:22 DEBUG      BEGIN Transaction
2024-03-19 12:38:22 DEBUG       sql=INSERT INTO llx_facture ( ref, entity, ref_ext, type, fk_soc, datec, remise_absolue, remise_percent, datef, date_pointoftax, note_private, note_public, ref_client, fk_account, module_source, pos_source, fk_fac_rec_source, fk_facture_source, fk_user_author, fk_projet, fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf, situation_cycle_ref, situation_counter, situation_final, fk_incoterms, location_incoterms, fk_multicurrency, multicurrency_code, multicurrency_tx, retained_warranty, retained_warranty_date_limit, retained_warranty_fk_cond_reglement) VALUES ('(PROV)', 1, 'VQ58317546', '0', 14, '2024-03-19 12:38:22', NULL, NULL, '2023-11-27 04:15:23', null, null, null, null, 3, null, null, null, null, 4, null, 0, 4, '2023-11-27 04:15:23', 'sponge', null, null, 0, 0, '', 0, 'TWD', 1, 0, NULL, 0)
2024-03-19 12:38:22 DEBUG       sql=UPDATE llx_facture SET ref='(PROV906)' WHERE rowid=906
2024-03-19 12:38:22 INFO        There is 2 lines that are array lines
2024-03-19 12:38:22 DEBUG       Facture::addline id=906, pu_ht=, qty=1, txtva=, txlocaltax1=, txlocaltax2=, fk_product=1, remise_percent=, date_start=1701058523, date_end=1701058523, ventil=, info_bits=, fk_remise_except=, price_base_type=HT, pu_ttc=0, type=, fk_unit=, desc=Original Chicken Biscuit …
2024-03-19 12:38:22 INFO         Product::fetch id=1 ref= ref_ext=
2024-03-19 12:38:22 DEBUG        sql=SELECT p.rowid, p.ref, p.ref_ext, p.label, p.description, p.url, p.note_public, p.note as note_private, p.customcode, p.fk_country, p.fk_state, p.lifetime, p.qc_frequency, p.price, p.price_ttc, p.price_min, p.price_min_ttc, p.price_base_type, p.cost_price, p.default_vat_code, p.tva_tx, p.recuperableonly as tva_npr, p.localtax1_tx, p.localtax2_tx, p.localtax1_type, p.localtax2_type, p.tosell, p.tobuy, p.fk_product_type, p.duration, p.fk_default_warehouse, p.fk_default_workstation, p.seuil_stock_alerte, p.canvas, p.net_measure, p.net_measure_units, p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units, p.barcode, p.fk_barcode_type, p.finished, p.fk_default_bom, p.mandatory_period, p.accountancy_code_buy, p.accountancy_code_buy_intra, p.accountancy_code_buy_export, p.accountancy_code_sell, p.accountancy_code_sell_intra, p.accountancy_code_sell_export, p.pmp, p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit, p.fk_price_expression, p.price_autogen, p.model_pdf, p.stock FROM llx_product as p WHERE p.rowid = 1
2024-03-19 12:38:22 DEBUG        sql=SELECT rowid, name, label, type, size, elementtype, fieldunique, fieldrequired, param, pos, alwayseditable, perms, langs, list, printable, totalizable, fielddefault, fieldcomputed, entity, enabled, help, css, cssview, csslist FROM llx_extrafields WHERE elementtype = 'product' ORDER BY pos
2024-03-19 12:38:22 DEBUG        sql=SELECT rowid, monitor, tipo_plato FROM llx_product_extrafields WHERE fk_object = 1
2024-03-19 12:38:22 INFO         getLocalTaxesFromRate vatrate=0 local=0
2024-03-19 12:38:22 DEBUG        sql=SELECT t.taux as rate, t.code, t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.accountancy_code_sell, t.accountancy_code_buy FROM llx_c_tva as t, llx_c_country as c WHERE t.fk_pays = c.rowid AND c.code = 'TW' AND t.taux = 0 AND t.active = 1
2024-03-19 12:38:22 INFO         get_localtax tva=0 local=1 thirdparty_buyer id=/country_code= thirdparty_seller id=0/country_code=TW thirdparty_seller localtax1_assuj=0  thirdparty_seller localtax2_assuj=0
2024-03-19 12:38:22 INFO         get_localtax tva=0 local=2 thirdparty_buyer id=/country_code= thirdparty_seller id=0/country_code=TW thirdparty_seller localtax1_assuj=0  thirdparty_seller localtax2_assuj=0
2024-03-19 12:38:22 INFO         Price.lib::calcul_price_total qty=1 pu= remise_percent_ligne=0 txtva=0 uselocaltax1_rate=0 uselocaltax2_rate=0 remise_percent_global=0 price_base_type=HT type=0 progress=100
2024-03-19 12:38:22 INFO         Price.lib::calcul_price_total MAIN_ROUNDING_RULE_TOT= pu=0 qty=1 price_base_type=HT total_ht=0-total_vat=0-total_ttc=0
2024-03-19 12:38:22 DEBUG        FactureLigne::insert rang=0
2024-03-19 12:38:22 INFO         Product::fetch id=1 ref= ref_ext=
2024-03-19 12:38:22 DEBUG        sql=SELECT p.rowid, p.ref, p.ref_ext, p.label, p.description, p.url, p.note_public, p.note as note_private, p.customcode, p.fk_country, p.fk_state, p.lifetime, p.qc_frequency, p.price, p.price_ttc, p.price_min, p.price_min_ttc, p.price_base_type, p.cost_price, p.default_vat_code, p.tva_tx, p.recuperableonly as tva_npr, p.localtax1_tx, p.localtax2_tx, p.localtax1_type, p.localtax2_type, p.tosell, p.tobuy, p.fk_product_type, p.duration, p.fk_default_warehouse, p.fk_default_workstation, p.seuil_stock_alerte, p.canvas, p.net_measure, p.net_measure_units, p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units, p.barcode, p.fk_barcode_type, p.finished, p.fk_default_bom, p.mandatory_period, p.accountancy_code_buy, p.accountancy_code_buy_intra, p.accountancy_code_buy_export, p.accountancy_code_sell, p.accountancy_code_sell_intra, p.accountancy_code_sell_export, p.pmp, p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit, p.fk_price_expression, p.price_autogen, p.model_pdf, p.stock FROM llx_product as p WHERE p.rowid = 1
2024-03-19 12:38:22 DEBUG        sql=SELECT rowid, monitor, tipo_plato FROM llx_product_extrafields WHERE fk_object = 1
2024-03-19 12:38:22 DEBUG        ProductFournisseur::find_min_price_product_fournisseur
2024-03-19 12:38:22 DEBUG        sql=SELECT s.nom as supplier_name, s.rowid as fourn_id, pfp.rowid as product_fourn_price_id, pfp.ref_fourn, pfp.price, pfp.quantity, pfp.unitprice, pfp.tva_tx, pfp.charges, pfp.remise, pfp.remise_percent, pfp.fk_supplier_price_expression, pfp.delivery_time_days ,pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code FROM llx_societe as s, llx_product_fournisseur_price as pfp WHERE s.entity IN (1) AND pfp.entity IN (1) AND pfp.fk_product = 1 AND pfp.fk_soc = s.rowid AND s.status = 1
2024-03-19 12:38:22 DEBUG        CommonObject::isExistingObject
2024-03-19 12:38:22 DEBUG        sql=SELECT rowid, ref, ref_ext FROM llx_product WHERE entity IN (1) AND rowid = 1
2024-03-19 12:38:22 DEBUG         FactureLigne::insert
2024-03-19 12:38:22 DEBUG         sql=INSERT INTO llx_facturedet (fk_facture, fk_parent_line, label, description, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, fk_product, product_type, remise_percent, subprice, ref_ext, fk_remise_except, date_start, date_end, fk_code_ventilation,  rang, special_code, fk_product_fournisseur_price, buy_price_ht, info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2, situation_percent, fk_prev_id, fk_unit, fk_user_author, fk_user_modif, fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc) VALUES (906, null, null, 'Original Chicken Biscuit $370.0', 1, '', 0, 0, 0, '0', '0', 1, 0, 0, 0, '', null, '2023-11-27 04:15:23', '2023-11-27 04:15:23', 0, 0, 0, null, 0, '0', 0, 0, 0, 0, 0, 100, null, NULL, 4, 4, 0, 'TWD', 0, 0, 0, 0)
2024-03-19 12:38:22 DEBUG       Facture::addline id=906, pu_ht=, qty=1, txtva=, txlocaltax1=, txlocaltax2=, fk_product=1, remise_percent=, date_start=1701058523, date_end=1701058523, ventil=, info_bits=, fk_remise_except=, price_base_type=HT, pu_ttc=0, type=, fk_unit=, desc=Original Chicken Biscuit …
2024-03-19 12:38:22 INFO         Product::fetch id=1 ref= ref_ext=
2024-03-19 12:38:22 DEBUG        sql=SELECT p.rowid, p.ref, p.ref_ext, p.label, p.description, p.url, p.note_public, p.note as note_private, p.customcode, p.fk_country, p.fk_state, p.lifetime, p.qc_frequency, p.price, p.price_ttc, p.price_min, p.price_min_ttc, p.price_base_type, p.cost_price, p.default_vat_code, p.tva_tx, p.recuperableonly as tva_npr, p.localtax1_tx, p.localtax2_tx, p.localtax1_type, p.localtax2_type, p.tosell, p.tobuy, p.fk_product_type, p.duration, p.fk_default_warehouse, p.fk_default_workstation, p.seuil_stock_alerte, p.canvas, p.net_measure, p.net_measure_units, p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units, p.barcode, p.fk_barcode_type, p.finished, p.fk_default_bom, p.mandatory_period, p.accountancy_code_buy, p.accountancy_code_buy_intra, p.accountancy_code_buy_export, p.accountancy_code_sell, p.accountancy_code_sell_intra, p.accountancy_code_sell_export, p.pmp, p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit, p.fk_price_expression, p.price_autogen, p.model_pdf, p.stock FROM llx_product as p WHERE p.rowid = 1
2024-03-19 12:38:22 DEBUG        sql=SELECT rowid, monitor, tipo_plato FROM llx_product_extrafields WHERE fk_object = 1
2024-03-19 12:38:22 INFO         getLocalTaxesFromRate vatrate=0 local=0
2024-03-19 12:38:22 DEBUG        sql=SELECT t.taux as rate, t.code, t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.accountancy_code_sell, t.accountancy_code_buy FROM llx_c_tva as t, llx_c_country as c WHERE t.fk_pays = c.rowid AND c.code = 'TW' AND t.taux = 0 AND t.active = 1
2024-03-19 12:38:22 INFO         get_localtax tva=0 local=1 thirdparty_buyer id=/country_code= thirdparty_seller id=0/country_code=TW thirdparty_seller localtax1_assuj=0  thirdparty_seller localtax2_assuj=0
2024-03-19 12:38:22 INFO         get_localtax tva=0 local=2 thirdparty_buyer id=/country_code= thirdparty_seller id=0/country_code=TW thirdparty_seller localtax1_assuj=0  thirdparty_seller localtax2_assuj=0
2024-03-19 12:38:22 INFO         Price.lib::calcul_price_total qty=1 pu= remise_percent_ligne=0 txtva=0 uselocaltax1_rate=0 uselocaltax2_rate=0 remise_percent_global=0 price_base_type=HT type=0 progress=100
2024-03-19 12:38:22 INFO         Price.lib::calcul_price_total MAIN_ROUNDING_RULE_TOT= pu=0 qty=1 price_base_type=HT total_ht=0-total_vat=0-total_ttc=0
2024-03-19 12:38:22 DEBUG        FactureLigne::insert rang=0
2024-03-19 12:38:22 INFO         Product::fetch id=1 ref= ref_ext=
2024-03-19 12:38:22 DEBUG        sql=SELECT p.rowid, p.ref, p.ref_ext, p.label, p.description, p.url, p.note_public, p.note as note_private, p.customcode, p.fk_country, p.fk_state, p.lifetime, p.qc_frequency, p.price, p.price_ttc, p.price_min, p.price_min_ttc, p.price_base_type, p.cost_price, p.default_vat_code, p.tva_tx, p.recuperableonly as tva_npr, p.localtax1_tx, p.localtax2_tx, p.localtax1_type, p.localtax2_type, p.tosell, p.tobuy, p.fk_product_type, p.duration, p.fk_default_warehouse, p.fk_default_workstation, p.seuil_stock_alerte, p.canvas, p.net_measure, p.net_measure_units, p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units, p.barcode, p.fk_barcode_type, p.finished, p.fk_default_bom, p.mandatory_period, p.accountancy_code_buy, p.accountancy_code_buy_intra, p.accountancy_code_buy_export, p.accountancy_code_sell, p.accountancy_code_sell_intra, p.accountancy_code_sell_export, p.pmp, p.datec, p.tms, p.import_key, p.entity, p.desiredstock, p.tobatch, p.batch_mask, p.fk_unit, p.fk_price_expression, p.price_autogen, p.model_pdf, p.stock FROM llx_product as p WHERE p.rowid = 1
2024-03-19 12:38:22 DEBUG        sql=SELECT rowid, monitor, tipo_plato FROM llx_product_extrafields WHERE fk_object = 1
2024-03-19 12:38:22 DEBUG        ProductFournisseur::find_min_price_product_fournisseur
2024-03-19 12:38:22 DEBUG        sql=SELECT s.nom as supplier_name, s.rowid as fourn_id, pfp.rowid as product_fourn_price_id, pfp.ref_fourn, pfp.price, pfp.quantity, pfp.unitprice, pfp.tva_tx, pfp.charges, pfp.remise, pfp.remise_percent, pfp.fk_supplier_price_expression, pfp.delivery_time_days ,pfp.multicurrency_price, pfp.multicurrency_unitprice, pfp.multicurrency_tx, pfp.fk_multicurrency, pfp.multicurrency_code FROM llx_societe as s, llx_product_fournisseur_price as pfp WHERE s.entity IN (1) AND pfp.entity IN (1) AND pfp.fk_product = 1 AND pfp.fk_soc = s.rowid AND s.status = 1
2024-03-19 12:38:22 DEBUG        CommonObject::isExistingObject
2024-03-19 12:38:22 DEBUG        sql=SELECT rowid, ref, ref_ext FROM llx_product WHERE entity IN (1) AND rowid = 1
2024-03-19 12:38:22 DEBUG         FactureLigne::insert
2024-03-19 12:38:22 DEBUG         sql=INSERT INTO llx_facturedet (fk_facture, fk_parent_line, label, description, qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, fk_product, product_type, remise_percent, subprice, ref_ext, fk_remise_except, date_start, date_end, fk_code_ventilation,  rang, special_code, fk_product_fournisseur_price, buy_price_ht, info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2, situation_percent, fk_prev_id, fk_unit, fk_user_author, fk_user_modif, fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc) VALUES (906, null, null, 'Original Chicken Biscuit $340.0', 1, '', 0, 0, 0, '0', '0', 1, 0, 0, 0, '', null, '2023-11-27 04:15:23', '2023-11-27 04:15:23', 0, 0, 0, null, 0, '0', 0, 0, 0, 0, 0, 100, null, NULL, 4, 4, 0, 'TWD', 0, 0, 0, 0)
2024-03-19 12:38:22 DEBUG       Facture::update_price
2024-03-19 12:38:22 DEBUG       sql=SELECT rowid, qty, subprice as up, remise_percent, total_ht, total_tva as total_tva, total_ttc, total_localtax1 as total_localtax1, total_localtax2 as total_localtax2, tva_tx as vatrate, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, info_bits, product_type, situation_percent, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc FROM llx_facturedet WHERE fk_facture = 906 AND product_type <> 9 ORDER by rowid
2024-03-19 12:38:22 INFO         Price.lib::calcul_price_total qty=1 pu=0.00000000 remise_percent_ligne=0 txtva=0.0000 uselocaltax1_rate=0 uselocaltax2_rate=0 remise_percent_global=0 price_base_type=HT type=0 progress=100
2024-03-19 12:38:22 INFO         Price.lib::calcul_price_total MAIN_ROUNDING_RULE_TOT= pu=0.00000000 qty=1 price_base_type=HT total_ht=0-total_vat=0-total_ttc=0
2024-03-19 12:38:22 INFO         Price.lib::calcul_price_total qty=1 pu=0.00000000 remise_percent_ligne=0 txtva=0.0000 uselocaltax1_rate=0 uselocaltax2_rate=0 remise_percent_global=0 price_base_type=HT type=0 progress=100
2024-03-19 12:38:22 INFO         Price.lib::calcul_price_total MAIN_ROUNDING_RULE_TOT= pu=0.00000000 qty=1 price_base_type=HT total_ht=0-total_vat=0-total_ttc=0
2024-03-19 12:38:22 DEBUG        Facture::update_price
2024-03-19 12:38:22 DEBUG        sql=UPDATE llx_facture SET total_ht = 0, total_tva = 0, localtax1 = 0, localtax2 = 0, total_ttc = 0, multicurrency_total_ht = 0, multicurrency_total_tva = 0, multicurrency_total_ttc = 0 WHERE rowid = 906
2024-03-19 12:38:22 INFO        Trigger 'ContactRoles' for action 'BILL_CREATE' launched by /var/www/html/core/triggers/interface_90_modSociete_ContactRoles.class.php. id=906
2024-03-19 12:38:22 DEBUG       Contact::getContactRoles
2024-03-19 12:38:22 DEBUG       sql=SELECT sc.fk_socpeople as id, sc.fk_c_type_contact FROM llx_c_type_contact tc, llx_societe_contacts sc INNER JOIN llx_socpeople sp ON sc.fk_socpeople = sp.rowid AND sp.statut = 1 WHERE sc.fk_soc =14 AND sc.fk_c_type_contact=tc.rowid AND tc.element = 'facture' AND tc.active = 1
2024-03-19 12:38:22 DEBUG      COMMIT Transaction
2024-03-19 12:38:22 INFO       --- End access to /api/index.php/invoices
JonBendtsen commented 7 months ago

I noticed your post on the Dolibarr forum, where I believe you mentioned that you used curl? but will post the same both places.

First my own setup Second my own invoice usage

Setup I recently made my own API changes to subscriptions linking with invoices. Existing invoices though. https://github.com/Dolibarr/dolibarr/pull/28930

Here's my setup and why I mentioned the curl thing.

I use the API explorer built into Dolibarr to get, post, put, ... JSON and perhaps change some parameters http://localhost/api/index.php/explorer/

That's the link to mine which I run inside a podman container using the v19 Tuxgasy image.

I also run a phpmyadmin container which peaks into Dolibarrs database such that I can see what is going on, as well as which tables and fields actually exist.

Next to this I have my VScodium editor where I edit the PHP files. If I make changes I can then quickly copy the files into my dolibarr container and test it out.

The first thing I do when I start my dolibarr container is to enter it, install git and then I just git init /var/www/html, and then a git commit. Because then I can always quickly revert any changes I made, and I can use git diff to see my changes inside the container, because the container image might not be 100% identical with the dolibarr git repo I have cloned to my laptop.

Own invoice usage I don't use invoices directly, I use the API to make a proposal, + the API to validate that proposal and then from that proposal I create an order. I have not had any issues with proposals, but what I do is to add premade products to my proposal, and then I let it calculate stuff itself. I do not set the price of the invoice.

In the file that you link to with API for the invoice, there is this line: require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';

I do not find any ref?

JonSweet16:dolibarr jonbendtsen$ grep 'public \$' htdocs/compta/facture/class/facture.class.php | grep ref
    public $ref_client;
    public $ref_customer;
    public $situation_cycle_ref;
    public $ref_ext; // External reference of the line

But I do find the total_ht and total_ttc

JonSweet16:dolibarr jonbendtsen$ grep 'public \$' htdocs/compta/facture/class/facture.class.php | grep total
    public $total_ht;
    public $total_tva;
    public $total_localtax1;
    public $total_localtax2;
    public $total_ttc;
    public $skip_update_total; // Skip update price total for special lines

When I made changes to the API to allow the subscription and invoice linking to go through, had to ensure that the objectlink was updated. https://github.com/Dolibarr/dolibarr/pull/28930/files

Now I actually did not develop this code myself, I simply searched for code somewhere else that handled the objectlink, and then I changed the indentation. I used grep to track down the places I needed to know. I started by finding which database tables was changed by running mysqldump before and after I changed something in the GUI.

Then I noticed the database tables, I started with grep to find files that would read or write to those database tables. In my case I found this file ./htdocs/core/class/commonobject.class.php and in that file I noticed the function add_object_linked.

Next grep was to see which files used that, and among several files I looked at ./htdocs/fourn/class/fournisseur.facture-rec.class.php which contained the exact code I needed. The only changes I did was indentation when I inserted it into htdocs/adherents/class/subscription.class.php

Maybe you can use the same method to track down where ref, total_ht and total_ttc are handled. I think the total variables are auto calculated from the products and their price on the invoices.