Files
tehran_taj/apps/api/scripts/report-name-split.cjs
T
s.esmaeiliandClaude Opus 5 7b598e9e8e parity(api): port the reference price formula with tests; SMS + MON-08 reports
Adds computeVariantPrice(), a verbatim port of the live formula at
henza_server/app/Models/ProductVariant.php:183-190, with the per-customer
making-charge offset as an explicit input defaulting to 0. The three
superseded formulas commented out above it apply VAT to the whole price
and are deliberately not ported (D4).

11 unit tests assert against hand calculations derived from the PHP source
rather than from the implementation, covering: with and without the
offset, VAT on the non-metal portion only, floor-to-nearest-1000, the
extraPercent default of 7, and the equivalence between the app folding the
offset into percent_pay and the server deriving it. 18 tests pass overall.

Also adds the D0 name-split review report script (read-only) and two
investigations the owner asked for:

- SMS: the reference uses the Trez panel (smspanel.trez.ir, Sms.php:69,97)
  with production credentials committed to git (SEC-12 — rotate them). All
  12 message templates catalogued; env var names proposed; nothing
  implemented pending approval.
- MON-08: henza_pwa computes percent_pay, discount and rial_pay rather
  than asking the user (cart.vue:170-179, Mixins/product/index.ts:69-75),
  so Phase 2 item #1 is to derive them server-side and ignore the client's
  values. rash_weight stays read from the DB as a recorded deviation.

Records the owner's two new standing rules: migrations only through the
standard Prisma workflow, and every data deletion logged in CHANGELOG.

[ref: henza_server/app/Models/{ProductVariant,Sms}.php, henza_pwa/src/views/cart.vue]

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 07:40:28 +03:30

87 lines
3.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* D0 requirement 3 — review report for names that cannot be split safely.
*
* The migration backfills firstName = first token, lastName = the rest. That is
* correct for two-token names and a guess for anything longer: "سید محمد
* حسینی" should almost certainly be firstName="سید محمد", and the migration
* writes firstName="سید" instead. Those rows need a human.
*
* READ-ONLY. It changes nothing; it prints what a person has to check.
*
* node scripts/report-name-split.cjs # print to stdout
* node scripts/report-name-split.cjs --csv out.csv
*
* Run it AFTER the 20260922120000_customer_name_parts migration.
*/
const { PrismaClient } = require('@prisma/client');
const fs = require('fs');
const prisma = new PrismaClient();
/** Same normalisation the migration's SQL uses. */
const tokens = (s) => String(s ?? '').trim().replace(/\s+/g, ' ').split(' ').filter(Boolean);
(async () => {
const customers = await prisma.$queryRawUnsafe(`
SELECT id, "fullName", "firstName", "lastName", mobile, "memberCode"
FROM "Customer"
ORDER BY "createdAt"
`);
const multi = [];
const empty = [];
const single = [];
for (const c of customers) {
const t = tokens(c.fullName);
if (t.length === 0) empty.push(c);
else if (t.length === 1) single.push(c);
else if (t.length > 2) multi.push({ ...c, tokenCount: t.length });
}
console.log(`Customers scanned: ${customers.length}`);
console.log(` two tokens (split is reliable): ${customers.length - multi.length - empty.length - single.length}`);
console.log(` one token (lastName is NULL): ${single.length}`);
console.log(` empty name (nothing to split): ${empty.length}`);
console.log(` MORE THAN TWO TOKENS — NEEDS REVIEW: ${multi.length}`);
if (single.length) {
console.log('\n--- single-token names (no surname recorded) ---');
for (const c of single) console.log(` ${c.id} ${c.mobile} "${c.fullName}"`);
}
if (empty.length) {
console.log('\n--- empty names ---');
for (const c of empty) console.log(` ${c.id} ${c.mobile}`);
}
if (multi.length) {
console.log('\n--- NEEDS REVIEW: more than two tokens ---');
console.log('The migration guessed first-token / rest. Verify each one.\n');
for (const c of multi) {
console.log(` ${c.id} ${c.mobile} (${c.tokenCount} tokens)`);
console.log(` fullName : "${c.fullName}"`);
console.log(` guessed : firstName="${c.firstName}" lastName="${c.lastName}"`);
}
}
const csvArg = process.argv.indexOf('--csv');
if (csvArg !== -1 && process.argv[csvArg + 1]) {
const esc = (v) => `"${String(v ?? '').replace(/"/g, '""')}"`;
const rows = [
['id', 'mobile', 'memberCode', 'fullName', 'guessedFirstName', 'guessedLastName', 'tokenCount'].join(','),
...multi.map((c) =>
[c.id, c.mobile, c.memberCode, c.fullName, c.firstName, c.lastName, c.tokenCount].map(esc).join(','),
),
];
// BOM so Excel opens the Persian text in the right encoding.
fs.writeFileSync(process.argv[csvArg + 1], '' + rows.join('\n'), 'utf8');
console.log(`\nCSV written: ${process.argv[csvArg + 1]} (${multi.length} rows)`);
}
await prisma.$disconnect();
})().catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});