Splits on orders are made by hand, so a third participant or an uneven share is a deliberate decision — and "Make it just me" deleted every split row regardless. A one-tap button silently destroying an arrangement made with more care than the tap that undid it is the same failure shape as the rewrite that dropped `settled`. Now it refuses when a participant other than the two consumers is present, or when the share is not 50. Verified against the running stack: a three-way split and a 70/30 both survive a press; a plain 50/50 still toggles off and back on. Also: the nudge reads share state instead of assuming false. Today a freshly ingested order has no splits — the 140 that do were split by hand after the backfill, not by a rule — but the label drives a destructive button, so a wrong assumption there costs data rather than a cosmetic error. One query is cheaper.
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { queryRow } from "@/lib/db";
|
||||||
import {
|
import {
|
||||||
parseOrderHTML,
|
parseOrderHTML,
|
||||||
parseOrderAmendment,
|
parseOrderAmendment,
|
||||||
@@ -11,7 +12,7 @@ import {
|
|||||||
NotAReceiptError,
|
NotAReceiptError,
|
||||||
type MessageMeta,
|
type MessageMeta,
|
||||||
} from "@/lib/order-ingestion";
|
} from "@/lib/order-ingestion";
|
||||||
import { merchantVerdict } from "@/lib/order-reviews";
|
import { merchantVerdict, SECOND_CONSUMER_ID } from "@/lib/order-reviews";
|
||||||
import { nudgeBlocks } from "@/lib/slack-blocks";
|
import { nudgeBlocks } from "@/lib/slack-blocks";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,6 +33,17 @@ function authorised(req: NextRequest): boolean {
|
|||||||
return !!got && got === expected;
|
return !!got && got === expected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Does a split with the second consumer already exist on this transaction? */
|
||||||
|
async function isShared(transactionId: number | null): Promise<boolean> {
|
||||||
|
if (!transactionId) return false;
|
||||||
|
const row = await queryRow<{ n: string }>(
|
||||||
|
`SELECT count(*) AS n FROM transaction_splits
|
||||||
|
WHERE transaction_id = $1 AND participant_id = $2`,
|
||||||
|
[transactionId, SECOND_CONSUMER_ID]
|
||||||
|
);
|
||||||
|
return Number(row?.n ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
if (!authorised(req)) {
|
if (!authorised(req)) {
|
||||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||||
@@ -113,7 +125,14 @@ export async function POST(req: NextRequest) {
|
|||||||
currency: order.currency,
|
currency: order.currency,
|
||||||
total: Number(order.totals.total_charged),
|
total: Number(order.totals.total_charged),
|
||||||
isFamily: order.is_family,
|
isFamily: order.is_family,
|
||||||
shared: false,
|
// Read rather than assume. Today a freshly ingested order has no
|
||||||
|
// splits, so `false` would be right — the 140 ingested orders
|
||||||
|
// that do carry splits were split by hand after the backfill,
|
||||||
|
// not by a rule. But the card's label drives a destructive
|
||||||
|
// button: if a split rule is ever added, an assumed `false`
|
||||||
|
// would label a shared order "Not shared" and offer to remove
|
||||||
|
// the split. One query is cheaper than that failure.
|
||||||
|
shared: await isShared(result.transactionId),
|
||||||
warn: verdict?.warn ?? false,
|
warn: verdict?.warn ?? false,
|
||||||
warnNote: verdict?.history.find((h) => h.note)?.note ?? null,
|
warnNote: verdict?.history.find((h) => h.note)?.note ?? null,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { verifySlackSignature, participantForSlackUser } from "@/lib/slack-verif
|
|||||||
import { nudgeBlocks, detailsModal } from "@/lib/slack-blocks";
|
import { nudgeBlocks, detailsModal } from "@/lib/slack-blocks";
|
||||||
import {
|
import {
|
||||||
RATINGS,
|
RATINGS,
|
||||||
|
OWNER_PARTICIPANT_ID,
|
||||||
SECOND_CONSUMER_ID,
|
SECOND_CONSUMER_ID,
|
||||||
merchantVerdict,
|
merchantVerdict,
|
||||||
type Rating,
|
type Rating,
|
||||||
@@ -200,10 +201,23 @@ async function handleModalSubmit(payload: {
|
|||||||
* 2026-01-09 — so there is no settled historical obligation to lose.
|
* 2026-01-09 — so there is no settled historical obligation to lose.
|
||||||
*/
|
*/
|
||||||
async function toggleShare(transactionId: number) {
|
async function toggleShare(transactionId: number) {
|
||||||
const existing = await queryRaw<{ participant_id: number }>(
|
const existing = await queryRaw<{ participant_id: number; share_percent: string }>(
|
||||||
`SELECT participant_id FROM transaction_splits WHERE transaction_id = $1`,
|
`SELECT participant_id, share_percent FROM transaction_splits WHERE transaction_id = $1`,
|
||||||
[transactionId]
|
[transactionId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Refuse to touch an arrangement this button cannot express. Splits are made
|
||||||
|
// by hand here, so a third participant or an uneven share is deliberate — and
|
||||||
|
// a one-tap button that silently flattened it would destroy a decision made
|
||||||
|
// with more care than the tap that undid it.
|
||||||
|
const foreign = existing.filter(
|
||||||
|
(e) => e.participant_id !== SECOND_CONSUMER_ID && e.participant_id !== OWNER_PARTICIPANT_ID
|
||||||
|
);
|
||||||
|
const uneven = existing.some(
|
||||||
|
(e) => e.participant_id === SECOND_CONSUMER_ID && Number(e.share_percent) !== 50
|
||||||
|
);
|
||||||
|
if (foreign.length || uneven) return;
|
||||||
|
|
||||||
if (existing.some((e) => e.participant_id === SECOND_CONSUMER_ID)) {
|
if (existing.some((e) => e.participant_id === SECOND_CONSUMER_ID)) {
|
||||||
await queryRaw(`DELETE FROM transaction_splits WHERE transaction_id = $1`, [
|
await queryRaw(`DELETE FROM transaction_splits WHERE transaction_id = $1`, [
|
||||||
transactionId,
|
transactionId,
|
||||||
|
|||||||
Reference in New Issue
Block a user