#!/bin/bash
# ---------------------------------------------------------------------------
# RidinFamily — Garde-fou n° 4 : Splash ne repasse JAMAIS en brouillon une
# facture validée qui doit rester inaltérable.
#
# CONSTAT (13/09/2026, journal inaltérable de erp.ridin-family.com)
# StatusTrait::setStatusFields() appelle setStatusDraft() sans condition :
#   - cas « Unknown » / « PaymentDraft » : « Whatever => Set Draft » ;
#   - cas « PaymentDue » : « If Already Paid => Set Draft ».
# Le connecteur WooCommerce annonce « Unknown (…) » pour tout statut non prévu,
# dont le statut maison partial-refund. Splash repasse alors la facture validée
# et payée en brouillon, réécrit ses lignes, supprime le règlement et revalide
# sous la même référence. FA2602-0777 : validée 250,00 le 04/02/2026, réécrite à
# 137,50 le 18/02 après une remise saisie sur la commande terminée ; règlement
# carte de 250,00 supprimé. 20 factures revalidées avec un autre montant.
# En France, journal inaltérable actif, c'est interdit ; et une modification
# faite dans la boutique après facturation doit devenir un AVOIR.
#
# CORRECTIF : avant tout retour en brouillon, refuser si
#   - le journal inaltérable (blockedlog) est actif, ou
#   - la facture porte de vrais règlements.
# La demande de la boutique est ignorée (journalisée) ; la réécriture des lignes
# qui suivrait échoue alors sur le statut validé de Dolibarr, et la synchro le
# signale au lieu de réécrire en silence.
#
# Usage :  ./patch_splash_draft_guard.sh [chemin_module_splash]            # vérification
#          ./patch_splash_draft_guard.sh [chemin_module_splash] --apply    # sauvegarde + patch
#          ./patch_splash_draft_guard.sh [chemin_module_splash] --revert   # restaure
# Chemin par défaut : le BANC erpdev.
# ---------------------------------------------------------------------------
set -euo pipefail

MOD="${1:-$HOME/ridindev/dolibarr/htdocs/custom/splash}"
ACTION="${2:-}"
case "$MOD" in --*) ACTION="$MOD"; MOD="$HOME/ridindev/dolibarr/htdocs/custom/splash";; esac
F="$MOD/src/Objects/Invoice/StatusTrait.php"
BAKDIR="$HOME/_modbak_splash_draftguard"
MARKER="MODIF Pichinov : jamais de retour en brouillon d une facture inalterable"

[ -f "$F" ] || { echo "ABSENT : $F"; exit 1; }
is_patched() { grep -q "$MARKER" "$F"; }

echo "=== Garde-fou n° 4 (retour en brouillon) — $F ==="
if is_patched; then echo "État : DÉJÀ patché"; else echo "État : NON patché"; fi

if [ "$ACTION" = "--revert" ]; then
  LAST=$(ls -1t "$BAKDIR"/StatusTrait.php.* 2>/dev/null | head -1 || true)
  [ -n "$LAST" ] || { echo "Aucune sauvegarde dans $BAKDIR"; exit 1; }
  cp "$LAST" "$F"; echo "Restauré depuis $LAST"; php -l "$F"; exit 0
fi
if [ "$ACTION" != "--apply" ]; then
  echo; echo "Vérification seule. Relancer avec --apply pour sauvegarder et patcher."; exit 0
fi
if is_patched; then echo "Rien à faire."; exit 0; fi

mkdir -p "$BAKDIR"
TS=$(date +%Y%m%d-%H%M%S)
cp "$F" "$BAKDIR/StatusTrait.php.$TS"
echo "Sauvegarde : $BAKDIR/StatusTrait.php.$TS"

php -r '
$f = $argv[1]; $s = file_get_contents($f);
$rep = function ($a, $b) use (&$s) {
    if (1 !== substr_count($s, $a)) { fwrite(STDERR, "ANCRE INTROUVABLE OU MULTIPLE :\n".$a."\n"); exit(1); }
    $s = str_replace($a, $b, $s);
};
$rep("use Splash\\Models\\Objects\\Invoice\\Status;\n",
     "use Splash\\Local\\Local;\nuse Splash\\Models\\Objects\\Invoice\\Status;\n");
$rep(
"                if ((0 != \$this->object->status) && (!\$this->setStatusDraft())) {
                    return false;
                }
                \$this->setInvoiceStatus(Facture::STATUS_DRAFT);",
"                if (0 != \$this->object->status) {
                    //====================================================================//
                    // MODIF Pichinov : jamais de retour en brouillon d une facture inalterable
                    if (\$this->isDraftForbidden()) {
                        break;
                    }
                    if (!\$this->setStatusDraft()) {
                        return false;
                    }
                }
                \$this->setInvoiceStatus(Facture::STATUS_DRAFT);");
$rep(
"                if (in_array((int) \$this->object->status, \$draftStatuses, false)) {
                    if (!\$this->setStatusDraft()) {",
"                if (in_array((int) \$this->object->status, \$draftStatuses, false)) {
                    // MODIF Pichinov : jamais de retour en brouillon d une facture inalterable
                    if (\$this->isDraftForbidden()) {
                        break;
                    }
                    if (!\$this->setStatusDraft()) {");
$rep(
"    /**
     * Set Invoice State as Draft
     *
     * @return bool
     */
    private function setStatusDraft(): bool",
"    /**
     * MODIF Pichinov : jamais de retour en brouillon d une facture inalterable.
     *
     * Back to draft, the connector rewrites the lines and validates again under
     * the same reference: what was issued is erased. With the unalterable log
     * active (mandatory in France) that is forbidden, and on an invoice that
     * carries real payments it silently drops money already recorded — seen on
     * a 250.00 card payment deleted and replaced by 137.50 after a discount was
     * typed on a completed order. A change made in the source after invoicing
     * must become a credit note, never an edit of the invoice.
     *
     * The source request is ignored and logged; any line rewrite that follows
     * then fails on the validated status, so the sync reports it instead of
     * rewriting in silence.
     *
     * @return bool True if the invoice must not go back to draft
     */
    private function isDraftForbidden(): bool
    {
        global \$db;

        if (Facture::STATUS_DRAFT == \$this->getInvoiceStatus()) {
            return false;
        }
        \$reason = null;
        if (function_exists(\"isModEnabled\") && isModEnabled(\"blockedlog\")) {
            \$reason = \"the unalterable log (blockedlog) is active\";
        } else {
            // Supplier invoices keep their payment links in another table.
            \$isSupplier = is_a(\$this, Local::CLASS_SUPPLIER_INVOICE);
            \$sql = \"SELECT COUNT(*) as nb FROM \".MAIN_DB_PREFIX
                .(\$isSupplier ? \"paiementfourn_facturefourn\" : \"paiement_facture\")
                .\" WHERE \".(\$isSupplier ? \"fk_facturefourn\" : \"fk_facture\").\" = \".((int) \$this->object->id);
            \$res = \$db->query(\$sql);
            \$obj = \$res ? \$db->fetch_object(\$res) : null;
            if (\$obj && ((int) \$obj->nb) > 0) {
                \$reason = ((int) \$obj->nb).\" real payment(s) recorded\";
            }
        }
        if (null === \$reason) {
            return false;
        }
        dol_syslog(
            \"splash: invoice \".\$this->object->ref.\" kept validated, \".\$reason
            .\", back-to-draft request from the source ignored\",
            LOG_WARNING
        );
        Splash::log()->war(
            \"Invoice \".\$this->object->ref.\" kept validated (\".\$reason.\"): \"
            .\"a change made after invoicing must be a credit note.\"
        );

        return true;
    }

    /**
     * Set Invoice State as Draft
     *
     * @return bool
     */
    private function setStatusDraft(): bool");
file_put_contents($f, $s);
echo "Patch appliqué.\n";
' "$F"
php -l "$F"
is_patched && echo ">>> GARDE-FOU N° 4 POSÉ"
