Compare commits
5
Commits
9234050a68
...
f0927bac88
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0927bac88 | ||
|
|
5aa00b2ffd | ||
|
|
b7490eb76a | ||
|
|
f00f680143 | ||
|
|
4ce1b12099 |
@@ -12,6 +12,7 @@ dependencies:
|
||||
- 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_tva
|
||||
- field.field.node.ligne_comptable.field_type_ligne
|
||||
- node.type.ligne_comptable
|
||||
module:
|
||||
@@ -47,7 +48,7 @@ content:
|
||||
third_party_settings: { }
|
||||
field_entree_liee:
|
||||
type: entity_reference_autocomplete
|
||||
weight: 2
|
||||
weight: 4
|
||||
region: content
|
||||
settings:
|
||||
match_operator: CONTAINS
|
||||
@@ -57,7 +58,7 @@ content:
|
||||
third_party_settings: { }
|
||||
field_flag:
|
||||
type: entity_reference_autocomplete_tags
|
||||
weight: 8
|
||||
weight: 11
|
||||
region: content
|
||||
settings:
|
||||
match_operator: CONTAINS
|
||||
@@ -67,21 +68,21 @@ content:
|
||||
third_party_settings: { }
|
||||
field_montant_ht:
|
||||
type: number
|
||||
weight: 3
|
||||
weight: 5
|
||||
region: content
|
||||
settings:
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
field_montant_ttc:
|
||||
type: number
|
||||
weight: 4
|
||||
weight: 7
|
||||
region: content
|
||||
settings:
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
field_notes:
|
||||
type: string_textarea
|
||||
weight: 6
|
||||
weight: 10
|
||||
region: content
|
||||
settings:
|
||||
rows: 5
|
||||
@@ -97,7 +98,7 @@ content:
|
||||
third_party_settings: { }
|
||||
field_repartition:
|
||||
type: paragraphs
|
||||
weight: 5
|
||||
weight: 9
|
||||
region: content
|
||||
settings:
|
||||
title: Répartition
|
||||
@@ -113,6 +114,13 @@ content:
|
||||
collapse_edit_all: collapse_edit_all
|
||||
duplicate: duplicate
|
||||
third_party_settings: { }
|
||||
field_tva:
|
||||
type: number
|
||||
weight: 8
|
||||
region: content
|
||||
settings:
|
||||
placeholder: ''
|
||||
third_party_settings: { }
|
||||
field_type_ligne:
|
||||
type: options_select
|
||||
weight: 1
|
||||
|
||||
@@ -12,6 +12,7 @@ dependencies:
|
||||
- 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_tva
|
||||
- field.field.node.ligne_comptable.field_type_ligne
|
||||
- node.type.ligne_comptable
|
||||
module:
|
||||
@@ -55,7 +56,7 @@ content:
|
||||
settings:
|
||||
link: true
|
||||
third_party_settings: { }
|
||||
weight: 8
|
||||
weight: 11
|
||||
region: content
|
||||
field_montant_ht:
|
||||
type: number_decimal
|
||||
@@ -66,7 +67,7 @@ content:
|
||||
scale: 2
|
||||
prefix_suffix: true
|
||||
third_party_settings: { }
|
||||
weight: 3
|
||||
weight: 5
|
||||
region: content
|
||||
field_montant_ttc:
|
||||
type: number_decimal
|
||||
@@ -77,14 +78,14 @@ content:
|
||||
scale: 2
|
||||
prefix_suffix: true
|
||||
third_party_settings: { }
|
||||
weight: 4
|
||||
weight: 7
|
||||
region: content
|
||||
field_notes:
|
||||
type: basic_string
|
||||
label: above
|
||||
settings: { }
|
||||
third_party_settings: { }
|
||||
weight: 6
|
||||
weight: 10
|
||||
region: content
|
||||
field_numero_facture:
|
||||
type: string
|
||||
@@ -102,7 +103,18 @@ content:
|
||||
view_mode: default
|
||||
link: ''
|
||||
third_party_settings: { }
|
||||
weight: 5
|
||||
weight: 9
|
||||
region: content
|
||||
field_tva:
|
||||
type: number_decimal
|
||||
label: above
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
decimal_separator: .
|
||||
scale: 2
|
||||
prefix_suffix: true
|
||||
third_party_settings: { }
|
||||
weight: 8
|
||||
region: content
|
||||
field_type_ligne:
|
||||
type: list_default
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
uuid: 9e9fc15c-9f7b-466a-bab0-fc6fe802a22f
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
config:
|
||||
- field.storage.node.field_tva
|
||||
- node.type.ligne_comptable
|
||||
id: node.ligne_comptable.field_tva
|
||||
field_name: field_tva
|
||||
entity_type: node
|
||||
bundle: ligne_comptable
|
||||
label: 'TVA (%)'
|
||||
description: ''
|
||||
required: false
|
||||
translatable: true
|
||||
default_value:
|
||||
-
|
||||
value: 10.0
|
||||
default_value_callback: ''
|
||||
settings:
|
||||
min: null
|
||||
max: null
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
field_type: decimal
|
||||
@@ -0,0 +1,20 @@
|
||||
uuid: 92ad1651-ab19-4c60-ad8c-4824fd97ecc3
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- node
|
||||
id: node.field_tva
|
||||
field_name: field_tva
|
||||
entity_type: node
|
||||
type: decimal
|
||||
settings:
|
||||
precision: 8
|
||||
scale: 4
|
||||
module: core
|
||||
locked: false
|
||||
cardinality: 1
|
||||
translatable: true
|
||||
indexes: { }
|
||||
persist_with_no_fields: false
|
||||
custom_storage: false
|
||||
@@ -0,0 +1,284 @@
|
||||
/* Ligne comptable add/edit form (node/add/ligne_comptable, node/X/edit --
|
||||
usually seen inside the "+ Ajouter une ligne" / "Modifier" AJAX modal
|
||||
from /lignes, see js/home.js's openAddForm/openEditForm). The default
|
||||
Drupal/Gin admin form widgets (tabledrag drag handles, the paragraphs
|
||||
"Toggle Actions" dropdown, vertical tabs rendered as an accordion) are
|
||||
built for content editors managing arbitrary nodes, not for the small
|
||||
numeric entry form associates fill in daily. This only restyles it
|
||||
into a compact grid -- no Form API behavior, validation, or field
|
||||
structure changes here (those live in figli_compta_ledger.module,
|
||||
which also hides the "advanced" accordion and "Published" checkbox
|
||||
entirely rather than just visually, since neither is ever used for a
|
||||
ligne_comptable).
|
||||
|
||||
Explicit local colors rather than Gin's --gin-* custom properties --
|
||||
this form isn't scoped inside #figli-home-app/#figli-dashboard-app, so
|
||||
the same "don't trust Gin's vars on custom routes" lesson from
|
||||
home.css/dashboard.css applies here too. */
|
||||
.figli-ledger-form {
|
||||
--flform-border: #dcdee2;
|
||||
--flform-border-soft: #e8eaed;
|
||||
--flform-label: #4b5563;
|
||||
--flform-text: #1a1a1a;
|
||||
--flform-bg-subtle: #f7f8fa;
|
||||
--flform-accent: #2f6f4f;
|
||||
--flform-danger: #b3261e;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.5rem 1rem;
|
||||
max-width: 44rem;
|
||||
margin: 0 auto;
|
||||
padding: 0.15rem 0.1rem 0.5rem;
|
||||
color: var(--flform-text);
|
||||
}
|
||||
/* Claro gives every .form-item a large margin-block (1.5rem, meant for a
|
||||
form with one field per row) -- with our own grid `gap` now doing that
|
||||
job, this just doubles up the spacing between rows. */
|
||||
.figli-ledger-form .form-item {
|
||||
margin-block: 0;
|
||||
}
|
||||
html.gin--dark-mode .figli-ledger-form {
|
||||
--flform-border: #3d3e42;
|
||||
--flform-border-soft: #333438;
|
||||
--flform-label: #a1a5ab;
|
||||
--flform-text: #e8e9ea;
|
||||
--flform-bg-subtle: #2a2b2e;
|
||||
--flform-accent: #5fb98a;
|
||||
--flform-danger: #ff6b6b;
|
||||
}
|
||||
|
||||
/* Wrapper divs Drupal still renders even though they're always empty
|
||||
here (single-language site, path aliases handled elsewhere, status
|
||||
hidden via #access in the module) -- without this they'd each still
|
||||
claim an empty grid cell. */
|
||||
.figli-ledger-form > .field--name-langcode,
|
||||
.figli-ledger-form > .field--name-path,
|
||||
.figli-ledger-form > .entity-content-form-footer,
|
||||
.figli-ledger-form > .form-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Row layout, fully explicit (not left to grid auto-placement) --
|
||||
there are now two separate 1-col-plus-2-col row groupings (Client +
|
||||
N° Facture, then Montant HT + TVA + Montant TTC) and relying on DOM
|
||||
order alone to keep them from bleeding into each other got fragile
|
||||
once a 4th financial field (TVA) entered the picture. This must stay
|
||||
in sync with the field weights set in
|
||||
figli_compta_ledger_update_8004()/_figli_compta_ledger_create_node_type_ligne_comptable(). */
|
||||
.figli-ledger-form > .field--name-title,
|
||||
.figli-ledger-form > .field--name-field-entree-liee,
|
||||
.figli-ledger-form > .field--name-field-repartition,
|
||||
.figli-ledger-form > .field--name-field-notes,
|
||||
.figli-ledger-form > .field--name-field-flag {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.figli-ledger-form > .field--name-field-date-ligne {
|
||||
grid-column: 1 / 2;
|
||||
}
|
||||
.figli-ledger-form > .field--name-field-type-ligne {
|
||||
grid-column: 2 / 4;
|
||||
}
|
||||
.figli-ledger-form > .field--name-field-client {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
.figli-ledger-form > .field--name-field-numero-facture {
|
||||
grid-column: 3 / 4;
|
||||
}
|
||||
.figli-ledger-form > .field--name-field-montant-ht {
|
||||
grid-column: 1 / 2;
|
||||
}
|
||||
/* The field_tva_rate select (added in figli_compta_ledger_form_alter(),
|
||||
not a real field so it gets no field--name-* class) fills the middle
|
||||
slot of the HT/TVA/TTC row instead -- see that function's #weight
|
||||
comment for why this specific ordering (weight 6, between HT's 5 and
|
||||
TTC's 7) matters, not just matching column numbers. field_tva itself
|
||||
only appears -- full width, its own row -- when "Autre" is picked. */
|
||||
.figli-ledger-form > .js-form-item-field-tva-rate {
|
||||
grid-column: 2 / 3;
|
||||
}
|
||||
.figli-ledger-form > .field--name-field-tva {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
.figli-ledger-form > .field--name-field-montant-ttc {
|
||||
grid-column: 3 / 4;
|
||||
}
|
||||
|
||||
/* Field basics */
|
||||
.figli-ledger-form .form-item__label {
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
color: var(--flform-label);
|
||||
margin-bottom: 0.2rem;
|
||||
display: inline-block;
|
||||
}
|
||||
.figli-ledger-form .form-item__description {
|
||||
font-size: 0.72rem;
|
||||
color: var(--flform-label);
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
.figli-ledger-form input.form-element,
|
||||
.figli-ledger-form select.form-element,
|
||||
.figli-ledger-form textarea.form-element {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--flform-border);
|
||||
border-radius: 6px;
|
||||
padding: 0.32rem 0.5rem;
|
||||
font-size: 0.88rem;
|
||||
color: var(--flform-text);
|
||||
}
|
||||
/* Not select: Claro positions its dropdown-chevron background-image via
|
||||
background-position/-repeat/-size, which the `background` shorthand
|
||||
below would silently reset to their initial values (top-left,
|
||||
repeating) -- that turned the single right-aligned arrow into a tiled
|
||||
pattern smeared across the whole field. Selects keep Claro's own
|
||||
background untouched; only the border/radius/padding above apply. */
|
||||
.figli-ledger-form input.form-element,
|
||||
.figli-ledger-form textarea.form-element {
|
||||
background: transparent;
|
||||
}
|
||||
.figli-ledger-form input.form-element:focus,
|
||||
.figli-ledger-form select.form-element:focus,
|
||||
.figli-ledger-form textarea.form-element:focus {
|
||||
outline: 2px solid var(--flform-accent);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
/* Montant TTC: #disabled, computed from HT + TVA (see
|
||||
figli_compta_ledger_node_presave()) -- a faint fill and full opacity
|
||||
(overriding the browser's default half-transparent disabled look,
|
||||
which would make the computed number harder to read) instead of the
|
||||
plain white/transparent of an editable field. -webkit-text-fill-color
|
||||
specifically: Chromium renders disabled/autofilled input text through
|
||||
this property instead of `color` -- left alone, it defaults to a
|
||||
fixed grey that's illegible against either theme's --flform-bg-subtle
|
||||
(near-invisible dark-grey-on-dark-grey in dark mode, the actual bug
|
||||
reported; same risk in light mode against a light fill). */
|
||||
.figli-ledger-form input:disabled.form-element {
|
||||
background: var(--flform-bg-subtle);
|
||||
color: var(--flform-text);
|
||||
-webkit-text-fill-color: var(--flform-text);
|
||||
opacity: 1;
|
||||
cursor: default;
|
||||
}
|
||||
.figli-ledger-form input[type="date"].form-element {
|
||||
width: auto;
|
||||
min-width: 9.5rem;
|
||||
}
|
||||
.figli-ledger-form textarea.form-element {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* Multi-value tables (Répartition, Entrée client liée): drop the drag
|
||||
handle and the "Show row weights" accessibility toggle for it -- row
|
||||
order never affects the répartition sum or anything displayed
|
||||
downstream, so with the usual 1-3 rows this is pure clutter. Remove
|
||||
isn't affected: it stays in the per-row "Toggle Actions" menu. */
|
||||
.figli-ledger-form .tabledrag-toggle-weight-wrapper,
|
||||
.figli-ledger-form td.field-multiple-drag {
|
||||
display: none;
|
||||
}
|
||||
.figli-ledger-form table.field-multiple-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: var(--flform-bg-subtle);
|
||||
border: 1px solid var(--flform-border-soft);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.figli-ledger-form table.field-multiple-table thead th {
|
||||
text-align: left;
|
||||
padding: 0.4rem 0.6rem 0.25rem;
|
||||
}
|
||||
.figli-ledger-form table.field-multiple-table tbody td {
|
||||
padding: 0.35rem 0.6rem;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid var(--flform-border-soft);
|
||||
}
|
||||
.figli-ledger-form .field-multiple-table .form-item__label {
|
||||
visibility: visible;
|
||||
}
|
||||
.figli-ledger-form .paragraphs-subform {
|
||||
display: flex;
|
||||
gap: 0.6rem;
|
||||
}
|
||||
.figli-ledger-form .paragraphs-subform > .js-form-wrapper {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Buttons: "Ajouter Répartition" / "Add another item", and the per-row
|
||||
Remove/Duplicate/Collapse actions -- all plain Drupal form-submit
|
||||
buttons by default, restyled as a small, consistent set instead of
|
||||
browser-default grey buttons of varying sizes. */
|
||||
.figli-ledger-form input.button {
|
||||
font-size: 0.78rem;
|
||||
padding: 0.3rem 0.65rem;
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--flform-border);
|
||||
background: transparent;
|
||||
color: var(--flform-text);
|
||||
cursor: pointer;
|
||||
}
|
||||
.figli-ledger-form input.button:hover {
|
||||
border-color: var(--flform-accent);
|
||||
color: var(--flform-accent);
|
||||
}
|
||||
.figli-ledger-form .field-add-more-submit {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
.figli-ledger-form .paragraphs-dropdown-toggle {
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--flform-label);
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
padding: 0.1rem 0.3rem;
|
||||
}
|
||||
.figli-ledger-form .paragraphs-dropdown-toggle:hover {
|
||||
color: var(--flform-accent);
|
||||
}
|
||||
/* Closed by default (Claro's own paragraphs.actions.css already sets
|
||||
display: none here and toggles a sibling .open class on click via
|
||||
paragraphs.actions.js) -- only restyle the panel's look, and only once
|
||||
Drupal's own JS has actually opened it. Getting this wrong (e.g.
|
||||
forcing `display: flex` unconditionally here) makes every "Toggle
|
||||
Actions" menu render permanently expanded. */
|
||||
.figli-ledger-form .paragraphs-dropdown.open .paragraphs-dropdown-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
padding: 0.3rem;
|
||||
border: 1px solid var(--flform-border);
|
||||
border-radius: 6px;
|
||||
background: var(--flform-bg-subtle);
|
||||
}
|
||||
|
||||
/* Field group heading style (the Répartition table's own label, inside
|
||||
its <th>) -- match the rest of the form's label typography instead of
|
||||
Drupal's default <h4>. */
|
||||
.figli-ledger-form table.field-multiple-table h4.form-item__label {
|
||||
font-size: 0.8rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* A répartition row flagged by the sum-mismatch #validate error (see
|
||||
figli_compta_ledger_validate_repartition()) -- kept subtle (a red
|
||||
outline, not a solid fill) to match the same red-liseré convention
|
||||
used for écarts in the /lignes table itself. */
|
||||
.figli-ledger-form tr.error,
|
||||
.figli-ledger-form .has-error {
|
||||
outline: 1px solid var(--flform-danger);
|
||||
outline-offset: -1px;
|
||||
}
|
||||
|
||||
/* Form actions (Save), rendered by the AJAX dialog's own button pane
|
||||
rather than here in most cases, but keep a sane baseline for the
|
||||
rare full-page (non-modal) view of this form. */
|
||||
.figli-ledger-form .form-actions input.button--primary {
|
||||
background: var(--flform-accent);
|
||||
color: #fff;
|
||||
border-color: var(--flform-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
@@ -211,6 +211,13 @@ function _figli_compta_ledger_create_node_type_ligne_comptable() {
|
||||
_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);
|
||||
// Scale 4 (not 2) so that reconstructing an existing invoice's exact
|
||||
// TTC from HT * (1 + TVA/100) round-trips to the centime for the
|
||||
// overwhelming majority of migrated lines -- see
|
||||
// figli_compta_ledger_update_8005()'s docblock for the empirical check
|
||||
// behind that choice. A user typing a plain rate like "20" is
|
||||
// unaffected either way.
|
||||
_figli_field('node', 'ligne_comptable', 'field_tva', 'TVA (%)', 'decimal', ['precision' => 8, 'scale' => 4, 'min' => 0]);
|
||||
_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');
|
||||
|
||||
@@ -235,11 +242,19 @@ function _figli_compta_ledger_create_node_type_ligne_comptable() {
|
||||
->setComponent('field_type_ligne', ['type' => 'options_select', 'weight' => 1])
|
||||
->setComponent('field_client', ['type' => 'entity_reference_autocomplete', 'weight' => 2])
|
||||
->setComponent('field_numero_facture', ['type' => 'string_textfield', 'weight' => 3])
|
||||
->setComponent('field_montant_ht', ['type' => 'number', 'weight' => 4])
|
||||
->setComponent('field_montant_ttc', ['type' => 'number', 'weight' => 5])
|
||||
->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])
|
||||
->setComponent('field_flag', ['type' => 'entity_reference_autocomplete_tags', 'weight' => 8])
|
||||
->setComponent('field_montant_ht', ['type' => 'number', 'weight' => 5])
|
||||
->setComponent('field_montant_ttc', ['type' => 'number', 'weight' => 7])
|
||||
// field_tva sits *after* Montant TTC, not between HT and TTC --
|
||||
// figli_compta_ledger_form_alter() inserts a non-field
|
||||
// "field_tva_rate" select at weight 6 (a select of the official
|
||||
// French VAT rates) to fill that visual slot instead; this real
|
||||
// field only becomes visible (still at its own weight, on its own
|
||||
// full-width row) when "Autre" is picked there. See
|
||||
// css/ledger-form.css's grid-column rules for both.
|
||||
->setComponent('field_tva', ['type' => 'number', 'weight' => 8])
|
||||
->setComponent('field_repartition', ['type' => 'paragraphs', 'weight' => 9, 'settings' => ['title' => 'Répartition', 'title_plural' => 'Répartitions', 'edit_mode' => 'open', 'add_mode' => 'button']])
|
||||
->setComponent('field_notes', ['type' => 'string_textarea', 'weight' => 10])
|
||||
->setComponent('field_flag', ['type' => 'entity_reference_autocomplete_tags', 'weight' => 11])
|
||||
->save();
|
||||
}
|
||||
|
||||
@@ -254,11 +269,12 @@ function _figli_compta_ledger_create_node_type_ligne_comptable() {
|
||||
->setComponent('field_type_ligne', ['type' => 'list_default', 'weight' => 1])
|
||||
->setComponent('field_client', ['type' => 'entity_reference_label', 'weight' => 2])
|
||||
->setComponent('field_numero_facture', ['type' => 'string', 'weight' => 3])
|
||||
->setComponent('field_montant_ht', ['type' => 'number_decimal', 'weight' => 4])
|
||||
->setComponent('field_montant_ttc', ['type' => 'number_decimal', 'weight' => 5])
|
||||
->setComponent('field_repartition', ['type' => 'entity_reference_revisions_entity_view', 'weight' => 6])
|
||||
->setComponent('field_notes', ['type' => 'basic_string', 'weight' => 7])
|
||||
->setComponent('field_flag', ['type' => 'entity_reference_label', 'weight' => 8])
|
||||
->setComponent('field_montant_ht', ['type' => 'number_decimal', 'weight' => 5])
|
||||
->setComponent('field_montant_ttc', ['type' => 'number_decimal', 'weight' => 7])
|
||||
->setComponent('field_tva', ['type' => 'number_decimal', 'weight' => 8])
|
||||
->setComponent('field_repartition', ['type' => 'entity_reference_revisions_entity_view', 'weight' => 9])
|
||||
->setComponent('field_notes', ['type' => 'basic_string', 'weight' => 10])
|
||||
->setComponent('field_flag', ['type' => 'entity_reference_label', 'weight' => 11])
|
||||
->save();
|
||||
}
|
||||
}
|
||||
@@ -353,3 +369,127 @@ function figli_compta_ledger_update_8003() {
|
||||
$view_display->setComponent('field_flag', ['type' => 'entity_reference_label', 'weight' => 8])->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds field_tva (TVA %) to ligne_comptable and switches Montant TTC from
|
||||
* manual entry to an automatically computed value (see
|
||||
* figli_compta_ledger_node_presave(): TTC = round(HT * (1 + TVA/100), 2)
|
||||
* from now on, whenever the line isn't a skip_validation-flagged
|
||||
* migration save). Existing content is backfilled separately, see
|
||||
* figli_compta_ledger_update_8005().
|
||||
*
|
||||
* Also reweights field_numero_facture (now next to Client, both
|
||||
* identifying "which invoice/client" this line is about) and
|
||||
* field_entree_liee (pushed just after it) so the HT/TVA/TTC trio can
|
||||
* form its own clean three-column row afterwards -- see
|
||||
* css/ledger-form.css's explicit grid-column rules, which this weight
|
||||
* order must stay in sync with.
|
||||
*/
|
||||
function figli_compta_ledger_update_8004() {
|
||||
_figli_field('node', 'ligne_comptable', 'field_tva', 'TVA (%)', 'decimal', ['precision' => 8, 'scale' => 4, 'min' => 0]);
|
||||
|
||||
// New lines default to 10% (taux intermédiaire) rather than blank --
|
||||
// most new lines associates enter do carry VAT, so this saves a click
|
||||
// on the common case instead of silently defaulting to "no VAT" for
|
||||
// every line unless someone remembers to change it.
|
||||
$field_tva = FieldConfig::loadByName('node', 'ligne_comptable', 'field_tva');
|
||||
if ($field_tva && $field_tva->getDefaultValueLiteral() === []) {
|
||||
$field_tva->setDefaultValue(10)->save();
|
||||
}
|
||||
|
||||
// Weights are plain integers -- entity_display component config
|
||||
// schema coerces anything else (a fractional weight silently rounds/
|
||||
// truncates to its integer part on save), so the row-grouping can't be
|
||||
// expressed as "insert field_tva between HT and TTC" via a fractional
|
||||
// weight the way it might be elsewhere; every field in this block gets
|
||||
// an explicit new integer instead.
|
||||
$form_display = EntityFormDisplay::load('node.ligne_comptable.default');
|
||||
if ($form_display) {
|
||||
$form_display->setComponent('field_numero_facture', ['type' => 'string_textfield', 'weight' => 3]);
|
||||
$form_display->setComponent('field_entree_liee', ['type' => 'entity_reference_autocomplete', 'weight' => 4] + (array) $form_display->getComponent('field_entree_liee'));
|
||||
$form_display->setComponent('field_montant_ht', ['type' => 'number', 'weight' => 5] + (array) $form_display->getComponent('field_montant_ht'));
|
||||
$form_display->setComponent('field_montant_ttc', ['type' => 'number', 'weight' => 7] + (array) $form_display->getComponent('field_montant_ttc'));
|
||||
// Weight 6 (between HT and TTC) is now the non-field
|
||||
// "field_tva_rate" select figli_compta_ledger_form_alter() adds --
|
||||
// field_tva itself moves after Répartition/Notes/Flag's *old*
|
||||
// positions, past the ones below, so it lands on its own row when
|
||||
// "Autre" reveals it instead of disrupting the HT/rate/TTC row.
|
||||
$form_display->setComponent('field_tva', ['type' => 'number', 'weight' => 8] + (array) $form_display->getComponent('field_tva'));
|
||||
$form_display->setComponent('field_repartition', ['weight' => 9] + (array) $form_display->getComponent('field_repartition'));
|
||||
$form_display->setComponent('field_notes', ['weight' => 10] + (array) $form_display->getComponent('field_notes'));
|
||||
$form_display->setComponent('field_flag', ['weight' => 11] + (array) $form_display->getComponent('field_flag'));
|
||||
$form_display->save();
|
||||
}
|
||||
|
||||
$view_display = EntityViewDisplay::load('node.ligne_comptable.default');
|
||||
if ($view_display) {
|
||||
$view_display->setComponent('field_tva', ['type' => 'number_decimal', 'weight' => 8] + (array) $view_display->getComponent('field_tva'));
|
||||
$view_display->setComponent('field_numero_facture', ['weight' => 3] + (array) $view_display->getComponent('field_numero_facture'));
|
||||
$view_display->setComponent('field_montant_ht', ['weight' => 5] + (array) $view_display->getComponent('field_montant_ht'));
|
||||
$view_display->setComponent('field_montant_ttc', ['weight' => 7] + (array) $view_display->getComponent('field_montant_ttc'));
|
||||
$view_display->setComponent('field_repartition', ['weight' => 9] + (array) $view_display->getComponent('field_repartition'));
|
||||
$view_display->setComponent('field_notes', ['weight' => 10] + (array) $view_display->getComponent('field_notes'));
|
||||
$view_display->setComponent('field_flag', ['weight' => 11] + (array) $view_display->getComponent('field_flag'));
|
||||
$view_display->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Backfills field_tva for existing ligne_comptable content from the
|
||||
* existing Montant HT / Montant TTC pair -- never touches Montant TTC
|
||||
* itself, so every historical value stays exactly as migrated (same
|
||||
* "never correct historical data" rule as everywhere else in this
|
||||
* module). Left blank where there's nothing to derive from (no TTC ever
|
||||
* recorded, or HT is zero).
|
||||
*
|
||||
* Why scale 4 on field_tva: checked empirically against all 1556
|
||||
* existing lignes_comptables before picking it. Of the 946 lines with a
|
||||
* real (non-zero, HT != TTC) rate, rounding the derived rate to 2
|
||||
* decimals and reconstructing TTC = HT * (1 + rate/100) mismatched the
|
||||
* real historical TTC (by more than a centime) for 74 of them --
|
||||
* unacceptably lossy for what's supposed to be a faithful backfill. At 4
|
||||
* decimals that drops to 9 (likely genuine blended/multi-rate invoices
|
||||
* collapsed into a single ligne_comptable, where no single "TVA %" can
|
||||
* be perfectly exact) -- an acceptable residual, and still purely
|
||||
* informational since Montant TTC itself is left untouched here either
|
||||
* way.
|
||||
*/
|
||||
function figli_compta_ledger_update_8005() {
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('node');
|
||||
$nids = $storage->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('type', 'ligne_comptable')
|
||||
->execute();
|
||||
|
||||
$filled = 0;
|
||||
$skipped = 0;
|
||||
// Only field_tva is written here -- montant_ttc is read but never
|
||||
// set, so this can never trip figli_compta_ledger_node_presave()'s
|
||||
// sum(répartition) == montant_ht check either way. skip_validation is
|
||||
// set anyway (a) for consistency with every other bulk-migration
|
||||
// script in this module and (b) because it also now suppresses the
|
||||
// TTC auto-computation added in the same presave function, which
|
||||
// would otherwise silently overwrite the untouched historical TTC the
|
||||
// moment this script calls save().
|
||||
\Drupal::state()->set('figli_compta_ledger.skip_validation', TRUE);
|
||||
foreach ($storage->loadMultiple($nids) as $node) {
|
||||
if (!$node->hasField('field_montant_ht') || $node->get('field_montant_ht')->isEmpty()
|
||||
|| !$node->hasField('field_montant_ttc') || $node->get('field_montant_ttc')->isEmpty()) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
$ht = (float) $node->get('field_montant_ht')->value;
|
||||
$ttc = (float) $node->get('field_montant_ttc')->value;
|
||||
if (abs($ht) < 0.0001) {
|
||||
$skipped++;
|
||||
continue;
|
||||
}
|
||||
$rate = round(($ttc / $ht - 1) * 100, 4);
|
||||
$node->set('field_tva', $rate);
|
||||
$node->save();
|
||||
$filled++;
|
||||
}
|
||||
\Drupal::state()->delete('figli_compta_ledger.skip_validation');
|
||||
|
||||
return "TVA calculée pour $filled lignes, $skipped laissées vides (pas de Montant TTC renseigné ou Montant HT nul).";
|
||||
}
|
||||
|
||||
@@ -35,6 +35,16 @@ dashboard_compte:
|
||||
- core/drupal
|
||||
- figli_compta_ledger/vue
|
||||
|
||||
ledger_form:
|
||||
js:
|
||||
js/ledger-form.js: {}
|
||||
css:
|
||||
theme:
|
||||
css/ledger-form.css: {}
|
||||
dependencies:
|
||||
- core/drupal
|
||||
- core/once
|
||||
|
||||
admin_chrome:
|
||||
css:
|
||||
theme:
|
||||
|
||||
@@ -35,6 +35,11 @@ function figli_compta_ledger_form_alter(&$form, FormStateInterface $form_state,
|
||||
if (!in_array($form_id, ['node_ligne_comptable_form', 'node_ligne_comptable_edit_form'], TRUE)) {
|
||||
return;
|
||||
}
|
||||
// The add and edit forms get different auto-generated classes
|
||||
// (node-ligne-comptable-form vs. node-ligne-comptable-edit-form) --
|
||||
// one shared class so css/ledger-form.css can target both without
|
||||
// duplicating every selector.
|
||||
$form['#attributes']['class'][] = 'figli-ledger-form';
|
||||
$form['#validate'][] = 'figli_compta_ledger_validate_repartition';
|
||||
|
||||
// Every save must create a revision, unconditionally -- there's no
|
||||
@@ -71,6 +76,123 @@ function figli_compta_ledger_form_alter(&$form, FormStateInterface $form_state,
|
||||
];
|
||||
}
|
||||
|
||||
// "Title" is generic Drupal boilerplate that means nothing to a
|
||||
// freelancer entering an accounting line -- but home.js/dashboard*.js
|
||||
// do fall back to it as the displayed "Libellé" when Notes / détail is
|
||||
// left empty, so it can't just be hidden. Relabel it so that
|
||||
// relationship is explicit instead of a mystery required field.
|
||||
if (isset($form['title']['widget'][0]['value'])) {
|
||||
$form['title']['widget'][0]['value']['#title'] = t('Libellé court');
|
||||
$form['title']['widget'][0]['value']['#description'] = t('Affiché dans le grand livre si "Notes / détail" ci-dessous est laissé vide.');
|
||||
}
|
||||
|
||||
// Menu settings / URL alias / Authoring information: none of this is
|
||||
// ever used for a ligne_comptable (no menu links, no path aliases, and
|
||||
// the acting user + revision timestamp are already captured by the
|
||||
// forced revision in figli_compta_ledger_node_presave() below) -- just
|
||||
// noise in a form meant to be a quick numeric entry, not a full content
|
||||
// editing screen. #access rather than deleting the keys: safe no-op if
|
||||
// a future field ever gets #group => 'advanced'.
|
||||
if (isset($form['advanced'])) {
|
||||
$form['advanced']['#access'] = FALSE;
|
||||
}
|
||||
|
||||
// "Published" is meaningless here too -- there's no draft/unpublished
|
||||
// workflow for ledger lines, every one of them is expected to count
|
||||
// immediately. Same pattern as the 'revision' field above: force the
|
||||
// value rather than exposing a checkbox nobody should ever touch.
|
||||
if (isset($form['status'])) {
|
||||
$form['status']['widget']['value']['#default_value'] = TRUE;
|
||||
$form['status']['#access'] = FALSE;
|
||||
}
|
||||
|
||||
// Montant TTC is computed from Montant HT + TVA (see
|
||||
// figli_compta_ledger_node_presave()) -- genuinely not editable
|
||||
// (#disabled, not just readonly: a disabled field can't be focused or
|
||||
// tabbed into at all, and Form API discards whatever a tampered
|
||||
// request submits for it in favor of #default_value, same protection
|
||||
// already used for 'revision' above). js/ledger-form.js can still set
|
||||
// its .value directly for the live preview -- #disabled only blocks
|
||||
// user interaction, not script writes.
|
||||
if (isset($form['field_montant_ttc']['widget'][0]['value'])) {
|
||||
$form['field_montant_ttc']['widget'][0]['value']['#disabled'] = TRUE;
|
||||
$form['field_montant_ttc']['widget'][0]['value']['#description'] = t('Calculé automatiquement à partir du montant HT et de la TVA.');
|
||||
}
|
||||
|
||||
// TVA: a select of the official French rates covers the overwhelming
|
||||
// majority of new lines with one click, instead of associates having
|
||||
// to know/type "20" or "5.5" by heart. "Autre" reveals the real
|
||||
// decimal field underneath for the rest -- crucially including every
|
||||
// pre-migration line, whose backfilled rate (see
|
||||
// figli_compta_ledger_update_8005()) is often a blended, non-standard
|
||||
// percentage that wouldn't match any of these options, and must stay
|
||||
// editable/visible as the exact figure it is rather than being forced
|
||||
// to snap to the nearest official rate.
|
||||
if (isset($form['field_tva']['widget'][0]['value'])) {
|
||||
$taux_officiels = [
|
||||
'0' => t('0 % (exonéré)'),
|
||||
'2.1' => t('2,1 % (taux particulier)'),
|
||||
'5.5' => t('5,5 % (taux réduit)'),
|
||||
'10' => t('10 % (taux intermédiaire)'),
|
||||
'20' => t('20 % (taux normal)'),
|
||||
];
|
||||
|
||||
$tva_field = &$form['field_tva']['widget'][0]['value'];
|
||||
$current = $tva_field['#default_value'] ?? NULL;
|
||||
$rate_default = 'autre';
|
||||
if ($current === NULL || $current === '') {
|
||||
// 10 % (taux intermédiaire), not 0 -- most new lines associates
|
||||
// enter do carry VAT, so this saves a click on the common case
|
||||
// instead of silently defaulting to "no VAT" for every line
|
||||
// unless someone remembers to change it.
|
||||
$rate_default = '10';
|
||||
}
|
||||
else {
|
||||
foreach (array_keys($taux_officiels) as $rate) {
|
||||
if (abs((float) $current - (float) $rate) < 0.0001) {
|
||||
$rate_default = $rate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A plain top-level element, deliberately *not* nested inside
|
||||
// $form['field_tva'] -- Field API's WidgetBase::extractFormValues()
|
||||
// reads the entire form_state value at that path as the field's own
|
||||
// delta-indexed value array, and chokes on an unrelated sibling key
|
||||
// mixed into it.
|
||||
//
|
||||
// Weight 6: the exact slot Montant HT (5)/Montant TTC (7) leave open
|
||||
// between them in figli_compta_ledger_update_8004() -- field_tva
|
||||
// itself has been moved to weight 8 (its own full-width row) so
|
||||
// that this select forms a clean HT/rate/TTC three-column row
|
||||
// whether or not "Autre" is currently revealing the real field_tva
|
||||
// input further down. See css/ledger-form.css's grid-column rules
|
||||
// for both -- the row layout there depends on this exact ordering,
|
||||
// not just on matching #weight values.
|
||||
$form['field_tva_rate'] = [
|
||||
'#type' => 'select',
|
||||
'#title' => t('TVA'),
|
||||
'#options' => $taux_officiels + ['autre' => t('Autre (préciser)')],
|
||||
'#default_value' => $rate_default,
|
||||
'#weight' => 6,
|
||||
];
|
||||
$tva_field['#title'] = t('Taux exact (%)');
|
||||
// Not #states: this element sits outside the field system (see the
|
||||
// comment above) and, tested in this exact AJAX-loaded modal,
|
||||
// #states' data-drupal-states attribute gets attached correctly but
|
||||
// never actually toggles visibility here -- unclear why (a working
|
||||
// #states dependency already exists a few fields up, on
|
||||
// field_entree_liee, watching a real Field API select rather than
|
||||
// this manually-added one), not worth chasing further when
|
||||
// js/ledger-form.js already has to run custom JS on this form
|
||||
// anyway for the HT/TVA -> TTC live preview. Plain JS toggle there
|
||||
// instead -- see figliLedgerTvaRateToggle.
|
||||
$form['#validate'][] = 'figli_compta_ledger_apply_tva_rate';
|
||||
}
|
||||
|
||||
$form['#attached']['library'][] = 'figli_compta_ledger/ledger_form';
|
||||
|
||||
$request = \Drupal::request();
|
||||
$wrapper_formats = ['drupal_ajax', 'drupal_modal', 'drupal_dialog'];
|
||||
$is_ajax_modal = in_array($request->query->get('_wrapper_format'), $wrapper_formats, TRUE)
|
||||
@@ -115,6 +237,28 @@ function figli_compta_ledger_validate_repartition(array &$form, FormStateInterfa
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Form #validate callback: when a standard French VAT rate was picked from
|
||||
* the field_tva_rate select (added in figli_compta_ledger_form_alter()),
|
||||
* copies it into field_tva's own submitted value -- that's the field
|
||||
* figli_compta_ledger_node_presave() actually reads to compute Montant TTC.
|
||||
* Left untouched when "Autre" is selected: the real field_tva input,
|
||||
* shown in that case by js/ledger-form.js's figliLedgerTvaRateToggle,
|
||||
* already carries whatever the associate typed there.
|
||||
*
|
||||
* Runs late enough to matter despite EntityForm::validateForm() building an
|
||||
* interim entity earlier in the same validate phase (for constraint
|
||||
* checking only) -- submitForm() rebuilds the entity fresh from
|
||||
* $form_state's values right before save(), by which point every
|
||||
* #validate callback, this one included, has already run.
|
||||
*/
|
||||
function figli_compta_ledger_apply_tva_rate(array &$form, FormStateInterface $form_state) {
|
||||
$rate = $form_state->getValue('field_tva_rate');
|
||||
if ($rate !== NULL && $rate !== 'autre') {
|
||||
$form_state->setValue(['field_tva', 0, 'value'], $rate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #ajax callback for the node form submit button: close the modal on
|
||||
* success, or re-render the form (with errors) in place on failure.
|
||||
@@ -163,8 +307,9 @@ function figli_compta_ledger_node_form_ajax_submit(array $form, FormStateInterfa
|
||||
* source spreadsheets' raw data, including known répartition mismatches --
|
||||
* those get surfaced as visible inconsistencies in the dashboard instead of
|
||||
* being silently fixed. Set the 'figli_compta_ledger.skip_validation' state
|
||||
* flag around such a bulk import to bypass this check; new lines entered by
|
||||
* associates through the form are never exempted.
|
||||
* flag around such a bulk import to bypass this check *and* the Montant TTC
|
||||
* auto-computation below; new lines entered by associates through the form
|
||||
* are never exempted from either.
|
||||
*/
|
||||
function figli_compta_ledger_node_presave(NodeInterface $node) {
|
||||
if ($node->bundle() !== 'ligne_comptable') {
|
||||
@@ -184,7 +329,7 @@ function figli_compta_ledger_node_presave(NodeInterface $node) {
|
||||
// silently mislabeling every edit with its predecessor's save time.
|
||||
$node->setRevisionCreationTime(\Drupal::time()->getRequestTime());
|
||||
|
||||
if (!$node->hasField('field_montant_ht') || !$node->hasField('field_repartition')) {
|
||||
if (!$node->hasField('field_montant_ht')) {
|
||||
return;
|
||||
}
|
||||
if (\Drupal::state()->get('figli_compta_ledger.skip_validation', FALSE)) {
|
||||
@@ -192,6 +337,38 @@ function figli_compta_ledger_node_presave(NodeInterface $node) {
|
||||
}
|
||||
|
||||
$montant_ht = (float) $node->get('field_montant_ht')->value;
|
||||
$tva = $node->hasField('field_tva') && !$node->get('field_tva')->isEmpty() ? (float) $node->get('field_tva')->value : 0.0;
|
||||
|
||||
// Montant TTC is no longer entered by hand -- it's a pure function of
|
||||
// Montant HT and TVA (see figli_compta_ledger_update_8004()). Only
|
||||
// recompute it when HT or TVA actually changed, though, rather than on
|
||||
// every save unconditionally: the TVA backfilled onto pre-migration
|
||||
// lines (figli_compta_ledger_update_8005()) is a best-effort
|
||||
// reconstruction and doesn't reproduce every historical Montant TTC to
|
||||
// the exact centime, so blindly recomputing on an unrelated edit
|
||||
// (fixing a typo in Libellé, say) would silently nudge an untouched
|
||||
// historical value -- exactly what this module's "never correct
|
||||
// historical data" rule exists to prevent. A genuinely new/changed
|
||||
// HT or TVA has no such history to protect.
|
||||
if ($node->hasField('field_montant_ttc')) {
|
||||
$ht_or_tva_changed = $node->isNew();
|
||||
if (!$ht_or_tva_changed && isset($node->original)) {
|
||||
$original = $node->original;
|
||||
$orig_ht = $original->hasField('field_montant_ht') && !$original->get('field_montant_ht')->isEmpty()
|
||||
? (float) $original->get('field_montant_ht')->value : NULL;
|
||||
$orig_tva = $original->hasField('field_tva') && !$original->get('field_tva')->isEmpty()
|
||||
? (float) $original->get('field_tva')->value : 0.0;
|
||||
$ht_or_tva_changed = $orig_ht !== $montant_ht || abs($orig_tva - $tva) > 0.00005;
|
||||
}
|
||||
if ($ht_or_tva_changed) {
|
||||
$node->set('field_montant_ttc', round($montant_ht * (1 + $tva / 100), 2));
|
||||
}
|
||||
}
|
||||
|
||||
if (!$node->hasField('field_repartition')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$somme = 0.0;
|
||||
foreach ($node->get('field_repartition')->referencedEntities() as $paragraph) {
|
||||
if ($paragraph->hasField('field_montant') && !$paragraph->get('field_montant')->isEmpty()) {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @file
|
||||
* Live preview of Montant TTC = Montant HT * (1 + TVA/100) on the ligne
|
||||
* comptable add/edit form -- purely cosmetic, so the associate sees the
|
||||
* computed amount before saving instead of a blank/stale readonly field.
|
||||
* The authoritative computation is server-side, in
|
||||
* figli_compta_ledger_node_presave() (figli_compta_ledger.module) --
|
||||
* whatever ends up in this input on submit is discarded and recomputed
|
||||
* there regardless of whether this script even ran.
|
||||
*/
|
||||
(function (Drupal, once) {
|
||||
'use strict';
|
||||
|
||||
Drupal.behaviors.figliLedgerTvaCalc = {
|
||||
attach: function (context) {
|
||||
// once()'s selector match runs through context.querySelectorAll(),
|
||||
// which -- like any querySelectorAll -- never matches the context
|
||||
// node itself, only its descendants. The AJAX dialog opening this
|
||||
// form passes the <form class="figli-ledger-form"> element itself
|
||||
// as context, so a selector of '.figli-ledger-form' silently
|
||||
// matched nothing and this behavior never ran. Selecting the HT
|
||||
// input directly sidesteps that: it's always a genuine descendant
|
||||
// of whatever context gets passed (document on a full page load,
|
||||
// the form itself from the dialog, or anything in between).
|
||||
once('figli-ledger-tva-calc', '[name="field_montant_ht[0][value]"]', context).forEach(function (ht) {
|
||||
var form = ht.closest('form');
|
||||
var tva = form.querySelector('[name="field_tva[0][value]"]');
|
||||
var rate = form.querySelector('[name="field_tva_rate"]');
|
||||
var ttc = form.querySelector('[name="field_montant_ttc[0][value]"]');
|
||||
if (!tva || !ttc) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The rate select (when not "Autre") is the actual source of
|
||||
// truth here -- figli_compta_ledger_apply_tva_rate() copies its
|
||||
// value into field_tva server-side on submit, but that happens
|
||||
// too late to matter for a live client-side preview.
|
||||
function currentTva() {
|
||||
if (rate && rate.value !== 'autre') {
|
||||
return parseFloat(rate.value);
|
||||
}
|
||||
return parseFloat(tva.value);
|
||||
}
|
||||
|
||||
function recompute() {
|
||||
var htValue = parseFloat(ht.value);
|
||||
if (isNaN(htValue)) {
|
||||
return;
|
||||
}
|
||||
var tvaValue = currentTva();
|
||||
if (isNaN(tvaValue)) {
|
||||
tvaValue = 0;
|
||||
}
|
||||
ttc.value = (htValue * (1 + tvaValue / 100)).toFixed(2);
|
||||
}
|
||||
|
||||
// Deliberately not called once up front on an existing (edit)
|
||||
// line: the stored Montant TTC may predate this computed-field
|
||||
// architecture and not be perfectly reproducible from HT/TVA to
|
||||
// the centime (see figli_compta_ledger_update_8005()'s
|
||||
// docblock) -- showing a recomputed number the instant the
|
||||
// modal opens, before the associate has touched anything, would
|
||||
// misrepresent what's actually saved. Only recompute once HT,
|
||||
// TVA, or the rate select are actually edited, matching the
|
||||
// same "only overwrite Montant TTC when HT/TVA actually
|
||||
// changed" guard in figli_compta_ledger_node_presave(). Harmless
|
||||
// no-op on a genuinely new/blank line either way (HT is empty,
|
||||
// recompute() bails out above).
|
||||
ht.addEventListener('input', recompute);
|
||||
tva.addEventListener('input', recompute);
|
||||
if (rate) {
|
||||
rate.addEventListener('change', recompute);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows/hides the real "Taux exact (%)" field_tva input based on the
|
||||
* field_tva_rate select added in figli_compta_ledger_form_alter() --
|
||||
* plain JS rather than #states: tested and confirmed #states' own
|
||||
* data-drupal-states attribute gets attached correctly here but never
|
||||
* actually toggles visibility, for reasons not tracked down (a
|
||||
* working #states dependency already exists a few fields up, on
|
||||
* field_entree_liee, watching a real Field API element rather than
|
||||
* this manually-added select -- that one difference is the only lead
|
||||
* so far). Not worth blocking on since this form already needs custom
|
||||
* JS anyway for the TTC live preview above.
|
||||
*/
|
||||
Drupal.behaviors.figliLedgerTvaRateToggle = {
|
||||
attach: function (context) {
|
||||
once('figli-ledger-tva-rate-toggle', '[name="field_tva_rate"]', context).forEach(function (select) {
|
||||
var form = select.closest('form');
|
||||
var exact = form.querySelector('.field--name-field-tva');
|
||||
if (!exact) {
|
||||
return;
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
exact.style.display = select.value === 'autre' ? '' : 'none';
|
||||
}
|
||||
|
||||
select.addEventListener('change', toggle);
|
||||
toggle();
|
||||
});
|
||||
}
|
||||
};
|
||||
})(Drupal, once);
|
||||
Reference in New Issue
Block a user