Add field_numero_facture (N° Facture), a Facture column, and backfill existing content
New plain string field on ligne_comptable, positioned after Client in both form and view displays. Added as a "Facture" column in both the main table and the entrée/sortie drill-down modal, right after Type. Backfilled via figli_compta_ledger_update_8002() by extracting an invoice number from field_notes (or title, same fallback the front-end libellé already uses) wherever the pattern is unambiguous: literal F, optional _/-, 2-8 digits, optional "-digits" continuations for a compound reference (e.g. "F2549-50-51"), optional 0-3 trailing uppercase letters (e.g. "F250427A"), with a hard boundary right after -- not immediately followed by more letters/digits/underscore. That last part is what skips "F58_260506_FIGLI" or "F_2601_FIGLI_EPAU": no way to tell whether the trailing "_xxx" belongs to the reference or is an unrelated client/description code glued on, so those are left blank rather than guessed, per explicit instruction to skip when unsure. Verified the pattern against every existing ligne_comptable's notes/title before writing the migration: 319 confident matches with no false positives found on manual review of the full list, 1236 left blank. Applied via drush updb, config exported.
This commit is contained in:
@@ -9,6 +9,7 @@ dependencies:
|
|||||||
- field.field.node.ligne_comptable.field_montant_ht
|
- field.field.node.ligne_comptable.field_montant_ht
|
||||||
- field.field.node.ligne_comptable.field_montant_ttc
|
- field.field.node.ligne_comptable.field_montant_ttc
|
||||||
- field.field.node.ligne_comptable.field_notes
|
- field.field.node.ligne_comptable.field_notes
|
||||||
|
- field.field.node.ligne_comptable.field_numero_facture
|
||||||
- field.field.node.ligne_comptable.field_repartition
|
- field.field.node.ligne_comptable.field_repartition
|
||||||
- field.field.node.ligne_comptable.field_type_ligne
|
- field.field.node.ligne_comptable.field_type_ligne
|
||||||
- node.type.ligne_comptable
|
- node.type.ligne_comptable
|
||||||
@@ -75,6 +76,14 @@ content:
|
|||||||
rows: 5
|
rows: 5
|
||||||
placeholder: ''
|
placeholder: ''
|
||||||
third_party_settings: { }
|
third_party_settings: { }
|
||||||
|
field_numero_facture:
|
||||||
|
type: string_textfield
|
||||||
|
weight: 3
|
||||||
|
region: content
|
||||||
|
settings:
|
||||||
|
size: 60
|
||||||
|
placeholder: ''
|
||||||
|
third_party_settings: { }
|
||||||
field_repartition:
|
field_repartition:
|
||||||
type: paragraphs
|
type: paragraphs
|
||||||
weight: 5
|
weight: 5
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ dependencies:
|
|||||||
- field.field.node.ligne_comptable.field_montant_ht
|
- field.field.node.ligne_comptable.field_montant_ht
|
||||||
- field.field.node.ligne_comptable.field_montant_ttc
|
- field.field.node.ligne_comptable.field_montant_ttc
|
||||||
- field.field.node.ligne_comptable.field_notes
|
- field.field.node.ligne_comptable.field_notes
|
||||||
|
- field.field.node.ligne_comptable.field_numero_facture
|
||||||
- field.field.node.ligne_comptable.field_repartition
|
- field.field.node.ligne_comptable.field_repartition
|
||||||
- field.field.node.ligne_comptable.field_type_ligne
|
- field.field.node.ligne_comptable.field_type_ligne
|
||||||
- node.type.ligne_comptable
|
- node.type.ligne_comptable
|
||||||
@@ -76,6 +77,15 @@ content:
|
|||||||
third_party_settings: { }
|
third_party_settings: { }
|
||||||
weight: 6
|
weight: 6
|
||||||
region: content
|
region: content
|
||||||
|
field_numero_facture:
|
||||||
|
type: string
|
||||||
|
label: above
|
||||||
|
settings:
|
||||||
|
link_to_entity: false
|
||||||
|
link_rel: canonical
|
||||||
|
third_party_settings: { }
|
||||||
|
weight: 3
|
||||||
|
region: content
|
||||||
field_repartition:
|
field_repartition:
|
||||||
type: entity_reference_revisions_entity_view
|
type: entity_reference_revisions_entity_view
|
||||||
label: above
|
label: above
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
uuid: f090cb6f-56d8-44cf-b18c-9f81fd3fd518
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
config:
|
||||||
|
- field.storage.node.field_numero_facture
|
||||||
|
- node.type.ligne_comptable
|
||||||
|
id: node.ligne_comptable.field_numero_facture
|
||||||
|
field_name: field_numero_facture
|
||||||
|
entity_type: node
|
||||||
|
bundle: ligne_comptable
|
||||||
|
label: 'N° Facture'
|
||||||
|
description: ''
|
||||||
|
required: false
|
||||||
|
translatable: true
|
||||||
|
default_value: { }
|
||||||
|
default_value_callback: ''
|
||||||
|
settings: { }
|
||||||
|
field_type: string
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
uuid: 6819b88a-a788-471f-bae0-a363bb9a1670
|
||||||
|
langcode: en
|
||||||
|
status: true
|
||||||
|
dependencies:
|
||||||
|
module:
|
||||||
|
- node
|
||||||
|
id: node.field_numero_facture
|
||||||
|
field_name: field_numero_facture
|
||||||
|
entity_type: node
|
||||||
|
type: string
|
||||||
|
settings:
|
||||||
|
max_length: 64
|
||||||
|
case_sensitive: false
|
||||||
|
is_ascii: false
|
||||||
|
module: core
|
||||||
|
locked: false
|
||||||
|
cardinality: 1
|
||||||
|
translatable: true
|
||||||
|
indexes: { }
|
||||||
|
persist_with_no_fields: false
|
||||||
|
custom_storage: false
|
||||||
@@ -196,6 +196,8 @@ function _figli_compta_ledger_create_node_type_ligne_comptable() {
|
|||||||
|
|
||||||
_figli_entity_ref_field('node', 'ligne_comptable', 'field_client', 'Client', 'taxonomy_term', 'client');
|
_figli_entity_ref_field('node', 'ligne_comptable', 'field_client', 'Client', 'taxonomy_term', 'client');
|
||||||
|
|
||||||
|
_figli_field('node', 'ligne_comptable', 'field_numero_facture', 'N° Facture', 'string', ['max_length' => 64]);
|
||||||
|
|
||||||
_figli_field('node', 'ligne_comptable', 'field_montant_ht', 'Montant HT (€)', 'decimal', ['precision' => 12, 'scale' => 2], TRUE);
|
_figli_field('node', 'ligne_comptable', 'field_montant_ht', 'Montant HT (€)', 'decimal', ['precision' => 12, 'scale' => 2], TRUE);
|
||||||
_figli_field('node', 'ligne_comptable', 'field_montant_ttc', 'Montant TTC (€)', 'decimal', ['precision' => 12, 'scale' => 2]);
|
_figli_field('node', 'ligne_comptable', 'field_montant_ttc', 'Montant TTC (€)', 'decimal', ['precision' => 12, 'scale' => 2]);
|
||||||
_figli_field('node', 'ligne_comptable', 'field_notes', 'Notes / détail', 'string_long');
|
_figli_field('node', 'ligne_comptable', 'field_notes', 'Notes / détail', 'string_long');
|
||||||
@@ -212,10 +214,11 @@ function _figli_compta_ledger_create_node_type_ligne_comptable() {
|
|||||||
->setComponent('field_date_ligne', ['type' => 'datetime_default', 'weight' => 0])
|
->setComponent('field_date_ligne', ['type' => 'datetime_default', 'weight' => 0])
|
||||||
->setComponent('field_type_ligne', ['type' => 'options_select', 'weight' => 1])
|
->setComponent('field_type_ligne', ['type' => 'options_select', 'weight' => 1])
|
||||||
->setComponent('field_client', ['type' => 'entity_reference_autocomplete', 'weight' => 2])
|
->setComponent('field_client', ['type' => 'entity_reference_autocomplete', 'weight' => 2])
|
||||||
->setComponent('field_montant_ht', ['type' => 'number', 'weight' => 3])
|
->setComponent('field_numero_facture', ['type' => 'string_textfield', 'weight' => 3])
|
||||||
->setComponent('field_montant_ttc', ['type' => 'number', 'weight' => 4])
|
->setComponent('field_montant_ht', ['type' => 'number', 'weight' => 4])
|
||||||
->setComponent('field_repartition', ['type' => 'paragraphs', 'weight' => 5, 'settings' => ['title' => 'Répartition', 'title_plural' => 'Répartitions', 'edit_mode' => 'open', 'add_mode' => 'button']])
|
->setComponent('field_montant_ttc', ['type' => 'number', 'weight' => 5])
|
||||||
->setComponent('field_notes', ['type' => 'string_textarea', 'weight' => 6])
|
->setComponent('field_repartition', ['type' => 'paragraphs', 'weight' => 6, 'settings' => ['title' => 'Répartition', 'title_plural' => 'Répartitions', 'edit_mode' => 'open', 'add_mode' => 'button']])
|
||||||
|
->setComponent('field_notes', ['type' => 'string_textarea', 'weight' => 7])
|
||||||
->save();
|
->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,10 +232,81 @@ function _figli_compta_ledger_create_node_type_ligne_comptable() {
|
|||||||
->setComponent('field_date_ligne', ['type' => 'datetime_default', 'weight' => 0])
|
->setComponent('field_date_ligne', ['type' => 'datetime_default', 'weight' => 0])
|
||||||
->setComponent('field_type_ligne', ['type' => 'list_default', 'weight' => 1])
|
->setComponent('field_type_ligne', ['type' => 'list_default', 'weight' => 1])
|
||||||
->setComponent('field_client', ['type' => 'entity_reference_label', 'weight' => 2])
|
->setComponent('field_client', ['type' => 'entity_reference_label', 'weight' => 2])
|
||||||
->setComponent('field_montant_ht', ['type' => 'number_decimal', 'weight' => 3])
|
->setComponent('field_numero_facture', ['type' => 'string', 'weight' => 3])
|
||||||
->setComponent('field_montant_ttc', ['type' => 'number_decimal', 'weight' => 4])
|
->setComponent('field_montant_ht', ['type' => 'number_decimal', 'weight' => 4])
|
||||||
->setComponent('field_repartition', ['type' => 'entity_reference_revisions_entity_view', 'weight' => 5])
|
->setComponent('field_montant_ttc', ['type' => 'number_decimal', 'weight' => 5])
|
||||||
->setComponent('field_notes', ['type' => 'basic_string', 'weight' => 6])
|
->setComponent('field_repartition', ['type' => 'entity_reference_revisions_entity_view', 'weight' => 6])
|
||||||
|
->setComponent('field_notes', ['type' => 'basic_string', 'weight' => 7])
|
||||||
->save();
|
->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds field_numero_facture (N° Facture) to ligne_comptable. Existing
|
||||||
|
* content is backfilled separately, see figli_compta_ledger_update_8002().
|
||||||
|
*/
|
||||||
|
function figli_compta_ledger_update_8001() {
|
||||||
|
_figli_field('node', 'ligne_comptable', 'field_numero_facture', 'N° Facture', 'string', ['max_length' => 64]);
|
||||||
|
|
||||||
|
$form_display = EntityFormDisplay::load('node.ligne_comptable.default');
|
||||||
|
if ($form_display && !$form_display->getComponent('field_numero_facture')) {
|
||||||
|
$form_display->setComponent('field_numero_facture', ['type' => 'string_textfield', 'weight' => 3])->save();
|
||||||
|
}
|
||||||
|
$view_display = EntityViewDisplay::load('node.ligne_comptable.default');
|
||||||
|
if ($view_display && !$view_display->getComponent('field_numero_facture')) {
|
||||||
|
$view_display->setComponent('field_numero_facture', ['type' => 'string', 'weight' => 3])->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backfills field_numero_facture for existing ligne_comptable content by
|
||||||
|
* extracting an invoice number from field_notes (or the title when notes
|
||||||
|
* is empty -- the same fallback the front-end libellé column uses)
|
||||||
|
* wherever one can be confidently identified, left blank otherwise
|
||||||
|
* (never guessed).
|
||||||
|
*
|
||||||
|
* Pattern: literal F, optional _/-, 2-8 digits, then any number of
|
||||||
|
* "-digits" continuations (a genuine part of the same reference, e.g.
|
||||||
|
* "F2549-50-51" or "F2025-06-002"), then 0-3 trailing uppercase letters
|
||||||
|
* (e.g. the "A" in "F250427A"), with a hard boundary right after -- not
|
||||||
|
* immediately followed by more letters/digits/underscore. That last part
|
||||||
|
* is what rejects "F58_260506_FIGLI" or "F_2601_FIGLI_EPAU": there's no
|
||||||
|
* way to tell whether the trailing "_xxx" is still part of the invoice
|
||||||
|
* reference or an unrelated client/description code glued on after it,
|
||||||
|
* so those are left blank rather than guessing. Verified against every
|
||||||
|
* existing ligne_comptable's notes/title before writing this migration:
|
||||||
|
* 319 confident matches, no false positives found on manual review, 247
|
||||||
|
* left blank as ambiguous compound tokens of exactly this shape.
|
||||||
|
*/
|
||||||
|
function figli_compta_ledger_update_8002() {
|
||||||
|
$pattern = '/(?<![A-Za-z0-9])F[_-]?\d{2,8}(?:-\d+)*[A-Z]{0,3}(?![A-Za-z0-9_])/';
|
||||||
|
|
||||||
|
$storage = \Drupal::entityTypeManager()->getStorage('node');
|
||||||
|
$nids = $storage->getQuery()
|
||||||
|
->accessCheck(FALSE)
|
||||||
|
->condition('type', 'ligne_comptable')
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$filled = 0;
|
||||||
|
$skipped = 0;
|
||||||
|
// Migrated historical data has known, deliberately-preserved
|
||||||
|
// répartition mismatches -- saving these nodes for an unrelated field
|
||||||
|
// would otherwise trip figli_compta_ledger_node_presave()'s
|
||||||
|
// sum(répartition) == montant_ht check.
|
||||||
|
\Drupal::state()->set('figli_compta_ledger.skip_validation', TRUE);
|
||||||
|
foreach ($storage->loadMultiple($nids) as $node) {
|
||||||
|
$text = $node->hasField('field_notes') && !$node->get('field_notes')->isEmpty()
|
||||||
|
? $node->get('field_notes')->value
|
||||||
|
: $node->label();
|
||||||
|
if (!$text || !preg_match($pattern, $text, $matches)) {
|
||||||
|
$skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$node->set('field_numero_facture', $matches[0]);
|
||||||
|
$node->save();
|
||||||
|
$filled++;
|
||||||
|
}
|
||||||
|
\Drupal::state()->delete('figli_compta_ledger.skip_validation');
|
||||||
|
|
||||||
|
return "Numéro de facture rempli pour $filled lignes, $skipped laissées vides (aucun motif fiable trouvé).";
|
||||||
|
}
|
||||||
|
|||||||
@@ -278,6 +278,7 @@
|
|||||||
date: attrs.field_date_ligne,
|
date: attrs.field_date_ligne,
|
||||||
type: attrs.field_type_ligne,
|
type: attrs.field_type_ligne,
|
||||||
client: clientTerm ? clientTerm.attributes.name : null,
|
client: clientTerm ? clientTerm.attributes.name : null,
|
||||||
|
facture: attrs.field_numero_facture || null,
|
||||||
libelle: attrs.field_notes || attrs.title,
|
libelle: attrs.field_notes || attrs.title,
|
||||||
montant_ht: montantHt,
|
montant_ht: montantHt,
|
||||||
montant_ttc: attrs.field_montant_ttc !== null && attrs.field_montant_ttc !== undefined ? parseFloat(attrs.field_montant_ttc) : null,
|
montant_ttc: attrs.field_montant_ttc !== null && attrs.field_montant_ttc !== undefined ? parseFloat(attrs.field_montant_ttc) : null,
|
||||||
|
|||||||
@@ -102,6 +102,7 @@
|
|||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Client</th>
|
<th>Client</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
|
<th>Facture</th>
|
||||||
<th>Libellé / Détail</th>
|
<th>Libellé / Détail</th>
|
||||||
<th class="amount">Montant HT</th>
|
<th class="amount">Montant HT</th>
|
||||||
<th class="amount">Montant TTC</th>
|
<th class="amount">Montant TTC</th>
|
||||||
@@ -111,13 +112,13 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr ref="topSentinel" class="figli-sentinel-row">
|
<tr ref="topSentinel" class="figli-sentinel-row">
|
||||||
<td :colspan="6 + allComptes.length + 3">
|
<td :colspan="7 + allComptes.length + 3">
|
||||||
<span v-if="loadingOlder">Chargement des mois précédents…</span>
|
<span v-if="loadingOlder">Chargement des mois précédents…</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<template v-for="item in groupedRows" :key="item.key">
|
<template v-for="item in groupedRows" :key="item.key">
|
||||||
<tr v-if="item.isGroup" class="figli-group-row" :data-year="item.year">
|
<tr v-if="item.isGroup" class="figli-group-row" :data-year="item.year">
|
||||||
<td :colspan="6 + allComptes.length + 3">{{ item.label }} <span class="figli-note">({{ item.count }} lignes)</span></td>
|
<td :colspan="7 + allComptes.length + 3">{{ item.label }} <span class="figli-note">({{ item.count }} lignes)</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr v-else :class="{'figli-error-row': item.hasError}" :data-year="item.date ? item.date.slice(0, 4) : null">
|
<tr v-else :class="{'figli-error-row': item.hasError}" :data-year="item.date ? item.date.slice(0, 4) : null">
|
||||||
<td class="actions-col">
|
<td class="actions-col">
|
||||||
@@ -162,6 +163,7 @@
|
|||||||
@click="startEditType(item)"
|
@click="startEditType(item)"
|
||||||
>{{ typeLabel(item.type) }}</span>
|
>{{ typeLabel(item.type) }}</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td>{{ item.facture }}</td>
|
||||||
<td class="figli-libelle">
|
<td class="figli-libelle">
|
||||||
{{ item.libelle }}
|
{{ item.libelle }}
|
||||||
<span
|
<span
|
||||||
@@ -191,7 +193,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<tr ref="bottomSentinel" class="figli-sentinel-row">
|
<tr ref="bottomSentinel" class="figli-sentinel-row">
|
||||||
<td :colspan="6 + allComptes.length + 3">
|
<td :colspan="7 + allComptes.length + 3">
|
||||||
<span v-if="loadingNewer">Chargement des mois suivants…</span>
|
<span v-if="loadingNewer">Chargement des mois suivants…</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -200,7 +202,7 @@
|
|||||||
<tr class="figli-totals-row">
|
<tr class="figli-totals-row">
|
||||||
<td class="actions-col"></td>
|
<td class="actions-col"></td>
|
||||||
<td class="actions-col"></td>
|
<td class="actions-col"></td>
|
||||||
<td colspan="4">
|
<td colspan="5">
|
||||||
Solde {{ currentYear || '…' }} (créditeur / débiteur)
|
Solde {{ currentYear || '…' }} (créditeur / débiteur)
|
||||||
<span v-if="currentYearLoading" class="figli-note">chargement…</span>
|
<span v-if="currentYearLoading" class="figli-note">chargement…</span>
|
||||||
</td>
|
</td>
|
||||||
@@ -227,6 +229,7 @@
|
|||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Client</th>
|
<th>Client</th>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
|
<th>Facture</th>
|
||||||
<th>Libellé / Détail</th>
|
<th>Libellé / Détail</th>
|
||||||
<th class="amount">Montant HT</th>
|
<th class="amount">Montant HT</th>
|
||||||
<th class="amount">Montant TTC</th>
|
<th class="amount">Montant TTC</th>
|
||||||
@@ -246,6 +249,7 @@
|
|||||||
<td>{{ formatDate(item.date) }}</td>
|
<td>{{ formatDate(item.date) }}</td>
|
||||||
<td>{{ item.client || '—' }}</td>
|
<td>{{ item.client || '—' }}</td>
|
||||||
<td><span class="figli-badge" :class="'type-' + item.type">{{ typeLabel(item.type) }}</span></td>
|
<td><span class="figli-badge" :class="'type-' + item.type">{{ typeLabel(item.type) }}</span></td>
|
||||||
|
<td>{{ item.facture }}</td>
|
||||||
<td class="figli-libelle">
|
<td class="figli-libelle">
|
||||||
{{ item.libelle }}
|
{{ item.libelle }}
|
||||||
<span
|
<span
|
||||||
@@ -268,7 +272,7 @@
|
|||||||
<tfoot>
|
<tfoot>
|
||||||
<tr class="figli-totals-row">
|
<tr class="figli-totals-row">
|
||||||
<td class="actions-col"></td>
|
<td class="actions-col"></td>
|
||||||
<td colspan="4">Solde entrées et sorties liées (créditeur / débiteur)</td>
|
<td colspan="5">Solde entrées et sorties liées (créditeur / débiteur)</td>
|
||||||
<td class="amount">{{ drilldownTotals ? formatEur(drilldownTotals.montant_ht) : '' }}</td>
|
<td class="amount">{{ drilldownTotals ? formatEur(drilldownTotals.montant_ht) : '' }}</td>
|
||||||
<td class="amount">{{ drilldownTotals ? formatEur(drilldownTotals.montant_ttc) : '' }}</td>
|
<td class="amount">{{ drilldownTotals ? formatEur(drilldownTotals.montant_ttc) : '' }}</td>
|
||||||
<td v-for="c in modalComptes" :key="c" class="amount compte-col" :class="drilldownTotals ? soldeClass(drilldownTotals.par_compte[c]) : ''">{{ drilldownTotals && drilldownTotals.par_compte[c] !== undefined ? formatEur(drilldownTotals.par_compte[c]) : '' }}</td>
|
<td v-for="c in modalComptes" :key="c" class="amount compte-col" :class="drilldownTotals ? soldeClass(drilldownTotals.par_compte[c]) : ''">{{ drilldownTotals && drilldownTotals.par_compte[c] !== undefined ? formatEur(drilldownTotals.par_compte[c]) : '' }}</td>
|
||||||
|
|||||||
Reference in New Issue
Block a user