dimesum

Accueil / Blog / Argent

Argent

Modifier une dépense partagée doit redéfinir le partage

· 9 min de lecture ·

Les valeurs par défaut d'une modification sont des bugs d'argent, donc Dimesum rend participants et split_type obligatoires sur chaque PATCH de dépense, aux côtés de base_version qui rejette une modification obsolète au lieu de la fusionner.

Corriger une faute de frappe ne devrait pas changer qui doit de l'argent. Dans Dimesum, modifier une dépense partagée redéfinit tout le partage. participants et split_type sont obligatoires sur chaque PATCH, et une modification qui omet l'un ou l'autre revient en 400 au lieu de remplir une valeur par défaut. Les valeurs par défaut de la création sont tous les membres actuels et un partage égal, qui sur une modification sont des bugs d'argent.

Deux échecs rendent le cas concret. Un colocataire arrivé en août est entraîné dans le dîner de juillet, parce que « tous les membres actuels » est évalué au moment où la modification arrive, non au moment où la dépense a été écrite. Un partage de loyer délibéré de 70/30 s'aplatit à 50/50, parce qu'un split_type absent signifie EQUAL. Aucun de ces échecs ne déclenche d'erreur, et tous deux déplacent de l'argent réel.

La position

Les valeurs par défaut d'une modification sont des bugs d'argent. Une valeur par défaut au moment de la création devine le groupe que l'auteur a sous les yeux à cet instant. Une modification arrive plus tard, contre un groupe qui a bougé, et la même supposition réécrit discrètement ce que les gens doivent.

Les valeurs par défaut de la création décrivent une nouvelle dépense, pas une ancienne

Au moment de la création, les valeurs par défaut sont honnêtes. L'auteur regarde le groupe tel qu'il est, et un partage égal entre tout le monde est le cas courant, donc Dimesum les remplit tous les deux. La dépense enregistre ses données saisies plutôt que ses seuls résultats : splits.percent_bp, splits.weight et splits.exact_minor conservent ce que l'utilisateur a tapé, de sorte qu'une modification ultérieure peut la rouvrir.

Une modification est un autre acte. La même dépense peut être corrigée des semaines plus tard, une fois qu'un nouveau colocataire est arrivé ou qu'un membre fantôme a été revendiqué. L'appartenance au groupe est une cible mouvante et le partage ne l'est pas. Réutiliser les valeurs par défaut de la création demande au groupe d'aujourd'hui de répondre à une question à laquelle l'ancienne dépense a déjà répondu.

Deux versions de la même modification : les valeurs par défaut héritées changent chaque part, tandis qu'un partage redéfini ne change que la description LA MODIFICATION HÉRITE DES VALEURS PAR DÉFAUT LA MODIFICATION REDÉFINIT LE PARTAGE Loyer, 20 000 euros, juillet Loyer, 20 000 euros, juillet v1 Asha 70%, 14 000 Bhavna 30%, 6 000 v1 Asha 70%, 14 000 Bhavna 30%, 6 000 Chetan rejoint l'appartement en août. Chetan rejoint l'appartement en août. PATCH corrige une faute, n'envoie aucun partage. PATCH corrige une faute, envoie le partage : participants: Asha, Bhavna split_type: PERCENT 7000 / 3000 v2 Asha 6 666,67 Bhavna 6 666,67 Chetan 6 666,66 v2 Asha 70%, 14 000 Bhavna 30%, 6 000 Chetan doit un mois qu'il n'a jamais vécu. Seule la description a changé.
La même modification d'un caractère, deux fois. Hériter des valeurs par défaut de la création repartage 20 000 euros en trois et facture un membre arrivé un mois plus tard ; redéfinir le partage laisse chaque part intacte.

Le refus de deviner n'est pas nouveau ici. Une dépense à plusieurs payeurs doit elle aussi redéfinir ses payeurs. soleStoredPayer réutilise le payeur enregistré seulement quand la dépense en a exactement un, si bien qu'une modification qui reste muette sur deux payeurs est refusée plutôt que réattribuée. Une modification qui ne dit rien des payeurs conserve le payeur propre à la dépense, jamais la personne qui effectue la modification.

L'API refuse une modification qui ne redéfinit pas son partage

La vérification de la passerelle s'exécute avant tout calcul d'argent. Quand participants est vide ou split_type est laissé blanc, la requête revient en 400 avec le code invalid_expense et le message « an edit must restate the split: participants and split_type are required ». Le service de dépenses répète la règle dans validateAmend, si bien qu'un appelant qui atteint le service par un autre chemin rencontre le même refus.

Ce qu'une création envoie face à ce qu'une modification doit redéfinir, sur POST et PATCH /v1/groups/{id}/expenses.
ChampÀ la créationSur une modificationCe que la valeur par défaut coûterait
participantsFacultatif. Par défaut, tous les membres actuelsObligatoireUn membre arrivé après rejoint une ancienne dépense
split_typeFacultatif. Par défaut, EQUALObligatoireUn partage 70/30 s'aplatit à 50/50
payersFacultatif. Par défaut, l'auteurOmis, conserve l'unique payeur de la dépense ; deux payeurs doivent être redéfinisCelui qui modifie devient le payeur, inversant qui doit à qui
base_versionNon envoyéObligatoire, et doit être égal à la version actuelleUne modification obsolète écrase un changement que son auteur n'a jamais lu
revision_idUUIDv7 client, la clé d'idempotenceIdem, un par versionUne modification réessayée facture le groupe deux fois
currencyIndiquée par dépenseDoit correspondre ; un changement est refuséUne ligne de soldes contient une seule devise par membre

La devise appartient à la même famille de refus. Une modification ne peut pas redénominer une dépense, parce qu'une ligne de soldes contient une seule devise par membre. Le côté écriture rejette le changement, et le grand livre en ajout seul met de côté un tel amendement si jamais il l'atteint. Refuser à la porte empêche les deux moitiés d'être en désaccord.

Une modification obsolète est rejetée pour son auteur, jamais fusionnée

base_version est l'autre moitié du contrat. Chaque PATCH porte la version que son auteur a lue, et checkTransition la compare à la ligne que la transaction vient de verrouiller. Égales, et la modification s'applique à la version plus un. Différentes, et l'appelant reçoit un HTTP 409 avec le code stale_version.

La fusion est l'alternative tentante, et elle est fausse. Deux modifications d'une même dépense sont deux énoncés complets de ce que la facture signifie. Les fusionner produit un troisième énoncé que personne n'a écrit, avec des parts qu'aucun des deux auteurs ne reconnaîtrait. Le rejet rend le conflit à la seule personne qui peut le résoudre.

Deux auteurs lisent la version 3 ; la première modification enregistre la version 4 et la seconde est refusée avec un 409 stale_version dépense, version 3 Auteur A PATCH base_version: 3 200 OK, version 4 Auteur B PATCH base_version: 3 409 stale_version Grand livre, une transaction : EXPENSE_REVERSAL de v3 EXPENSE v4 L'auteur B relit la version 4, réapplique le changement, envoie base_version: 4
Concurrence optimiste sur une dépense. La modification perdante est rendue à son auteur avec la version sur laquelle elle doit être reconstruite. La modification gagnante atteint le grand livre comme une annulation plus une réécriture, jamais une mise à jour.

L'idempotence et la concurrence sont tenues à l'écart l'une de l'autre à dessein. L'insertion de révision s'exécute avant la vérification de version, parce qu'une modification réessayée porte la version de base qu'elle a lue à l'origine, désormais obsolète. Une réexécution doit se lire comme une réexécution plutôt que comme un conflit, donc revision_id répond en premier et renvoie le résultat enregistré.

La réponse porte déjà ce dont la prochaine modification a besoin

Exiger davantage de champs sur un PATCH n'est juste que si un client peut les obtenir à peu de frais. Chaque réponse de dépense porte version, de sorte qu'un client qui vient d'écrire une dépense peut la modifier sans seconde lecture. La réponse de création, la réponse de modification et chaque ligne de liste portent le même champ.

Pour le client qui n'a pas écrit la dépense à l'instant, GET /v1/groups/{id}/expenses/{id} renvoie la version, les parts calculées et les données qui les sous-tendent. Ces données reviennent sous les mêmes noms qu'un PATCH accepte : participants, split_type, percents, weights, shares, items, pools. Un client lit une seule forme et la renvoie avec ses modifications, au lieu de traduire entre deux vocabulaires pour une même facture.

La réponse GET et la requête PATCH utilisent les mêmes noms de champ, version étant renommé base_version GET RENVOIE PATCH ACCEPTE version base_version participants participants split_type split_type percents, weights, shares percents, weights, shares items, pools items, pools renommé
Un seul vocabulaire, deux directions. Seul le champ version change de nom sur l'aller-retour, si bien qu'un client de modification n'entretient jamais de couche de traduction entre ce qu'il lit et ce qu'il écrit.

La symétrie compte le plus pour les partages qui ne peuvent pas être reconstruits. Un partage PERCENT ou une facture détaillée ne peut pas être redéfini à partir de ses seules parts résolues, parce que l'arrondi a déjà été appliqué et que les points de base et les lignes de détail ont disparu. Les instantanés de révision portent aussi les données saisies, de sorte que la feuille d'historique peut montrer ce qui était détaillé sur n'importe quelle version passée.

Obligatoire maintenant, parce qu'une exigence ne peut pas être ajoutée plus tard

Exiger un champ dès le premier jour est une décision sur l'avenir plutôt que sur aujourd'hui. Assouplir un champ obligatoire plus tard reste rétrocompatible : les clients l'envoient déjà, et le serveur commence à accepter les requêtes sans lui. Ajouter une exigence plus tard casse tous les clients qui comptaient sur l'ancienne valeur par défaut.

La direction se choisit donc une fois, tôt. Dimesum exige participants, split_type et base_version sur une modification tant que le nombre de clients est encore assez faible pour changer. Si une valeur par défaut sûre pour les modifications est un jour trouvée, les champs deviennent facultatifs et rien de ce qui est déjà livré ne cesse de fonctionner.

Faites qu'une modification redéfinisse ce qu'elle signifie

Les valeurs par défaut appartiennent à la création, où l'auteur peut voir le groupe auquel il consent. Sur une modification, les mêmes valeurs par défaut sont une supposition sur un groupe qui a depuis bougé. Votre prochaine étape : ouvrez votre propre point de terminaison de lecture et vérifiez qu'il rend les données de partage sous les noms de champ exacts que votre point de terminaison d'écriture accepte. Un client qui doit traduire entre les deux finira par en traduire un de travers.

Questions fréquentes

Pourquoi faut-il indiquer participants et split_type pour modifier une dépense partagée ?

Dimesum exige les deux champs parce que les valeurs par défaut de la création sont fausses pour une modification. À la création, Dimesum retient par défaut tous les membres actuels, avec un partage égal, ce qui correspond au groupe que l'auteur a sous les yeux. Une modification peut arriver des semaines plus tard, après l'arrivée de quelqu'un. Réutiliser ces valeurs par défaut ferait entrer un nouveau colocataire dans un ancien dîner et ramènerait un partage de loyer délibéré de 70/30 à 50/50, sans qu'aucune erreur ne s'affiche.

Que se passe-t-il quand deux personnes modifient la même dépense en même temps ?

La seconde modification est refusée avec un HTTP 409 et le code d'erreur stale_version. Chaque PATCH porte base_version, la version que son auteur a lue, et le chemin de modification la compare à la ligne verrouillée. Un écart signifie que la dépense a bougé, donc la modification revient à son auteur pour être réappliquée sur la version qu'il peut désormais voir. Rien n'est fusionné.

Modifier une dépense met-il à jour les écritures du grand livre directement ?

Non, modifier une dépense ne met jamais à jour une écriture du grand livre dans Dimesum. Une modification enregistre deux écritures dans une seule transaction : un EXPENSE_REVERSAL qui annule l'ancienne version poste par poste, puis une écriture EXPENSE pour la nouvelle version. UPDATE et DELETE sont retirés du rôle de base de données propre au grand livre, si bien qu'une mutation est impossible même pour du code bogué. Vous voyez un badge de modification et une feuille d'historique.

Faut-il un second appel API avant de modifier une dépense partagée ?

Non, un client qui vient d'écrire la dépense détient déjà la version dont une modification a besoin. Chaque réponse de dépense porte version, qui est ce que le prochain PATCH envoie comme base_version. Un client qui n'a pas écrit la dépense appelle GET /v1/groups/{id}/expenses/{id}, qui renvoie la version ainsi que les données de partage sous les mêmes noms de champ qu'un PATCH accepte.

Pourquoi exiger participants et split_type dès maintenant plutôt que les ajouter plus tard ?

Exiger un champ dès le premier jour est réversible, l'ajouter plus tard ne l'est pas. Assouplir un champ obligatoire plus tard reste rétrocompatible : les clients l'envoient déjà, et le serveur commence à accepter les requêtes sans lui. Ajouter une exigence plus tard casse tous les clients qui comptaient sur l'ancienne valeur par défaut, et dans une API d'argent, la casse est silencieuse jusqu'à ce que le solde de quelqu'un soit faux.