fix(eims): resolve Addis Ababa addresses against MoR's sub-city tier

MoR's location master has no zone level in a chartered city: CITY_NAME is the
sub-city and LOCALITY_DESC is the numbered woreda. e-Trade fills the gap by
repeating the region in ZONE, pushing the sub-city into CITY_NAME and the
woreda number into LOCALITY_DESC, so every Addis Ababa buyer failed CITY_NAME
lookup and could not be filed at all.

Retry one level down when ZONE repeats the region, accepting it only when
both shifted levels resolve exactly — an unknown zone still fails with its own
message. Match a zero-padded 07 against MoR's own WOREDA 7 spelling, and
add the reviewed AMAHARA / KOLFIE KERANIYO / MISRAK SHOA aliases.

Verified against the dev database: 2 of 40 companies resolved before, 31 after.
This commit is contained in:
Hagernesh
2026-09-01 03:01:56 +00:00
parent 3721ac1ef0
commit dd9f597e01
7 changed files with 269 additions and 17 deletions

View File

@@ -0,0 +1,100 @@
import { AppDataSource } from "../data-source";
/**
* Seeds the 30 test taxpayers the Ministry of Revenues issued for the EIMS/BSP
* **non-self buyer** certification run — the checklist item that needs a real
* invoice filed against a buyer that is not EDR itself.
*
* Every field comes from MoR's own roster, except the two location codes, which
* do not survive contact with the Ministry's own location master:
*
* - MoR's sheet gives `Region = "1"`. `PARISH_NO 1` is not an Ethiopian region
* at all (it is Djibouti), so the region is stored by name — `Bole` is an
* Addis Ababa sub-city, which fixes the region unambiguously (PARISH_NO 13).
* - MoR's sheet gives `City = "101"`, which is GOFA ZONE in SNNPRS. The buyer
* is in Bole, so the sub-city is stored by name (CITY_NO 78).
*
* In other words MoR does not validate the geographic codes it sends itself;
* these rows carry the addresses that actually resolve. The roster carries no
* woreda, so every row takes `NO WOREDA-144` — MoR's *own* "not specified"
* locality under BOLE (LOCALITY_NO 574), the same code EDR's static seller
* details already file under. Nothing here is invented.
*
* Idempotent: `ON CONFLICT (tin) DO NOTHING`. TIN 0089238373 is already on file
* as a real customer (Afri Software Solutions) and is deliberately left alone.
*/
/**
* `[TIN, phone, legal name, email, kebele?, house number?]`, verbatim from MoR's roster. The two
* trailing fields default to the values 22 of the 30 rows share.
*/
const KEBELE = "Near Bole Airport";
const HOUSE_NO = "123B";
const MOR_TEST_BUYERS: Array<[string, string, string, string, string?, string?]> = [
["0089238373", "251911091245", "Taxpayer A", "codethicaet@gmail.com", "Near Airport", "101"],
["0054864576", "251911091245", "Taxpayer B", "shehir8@gmail.com", "Near Airport"],
["0049056594", "251911091245", "Taxpayer C", "teme@odooethiopia.com", "Near Airport"],
["0059819904", "251911091245", "Taxpayer D", "rubiethoplc@gmail.com", "Near Airport"],
["0000018932", "251911091245", "Taxpayer E", "amanuelephremedu@gmail.com", "Near Airport"],
["0088683375", "251911091245", "Taxpayer F", "ermiastegegn576@gmail.com", "Near Airport"],
["0068421445", "251911091245", "Taxpayer G", "qelemmeda@gmail.com", "Near Airport"],
["0004404844", "251911091245", "Taxpayer H", "dagnegamu24@gmail.com"],
["0000037187", "251911091245", "Taxpayer I", "deresr.belay@gmail.com"],
["0050167460", "251911091245", "Taxpayer J", "sera2013ec@gmail.com"],
["0079690836", "251909978781", "Taxpayer K", "asmeradefa@gmail.com"],
["0068180813", "251944310004", "Taxpayer L", "hailelt@gmail.com"],
["0083907363", "251944310004", "Taxpayer M", "dawitfissha1@gmail.com"],
["0089032785", "251944310004", "Taxpayer N", "tewahido11@gmail.com"],
["0003826418", "251944310004", "Taxpayer O", "alemayehu.t@marakisoft.com"],
["0053374665", "251944310004", "Taxpayer P", "getlelaw@gmail.com"],
["0016175194", "251911463482", "Taxpayer Q", "abiye.abi@gmail.com", "Near Airport"],
["0094542975", "251911463482", "Taxpayer R", "abelgebreananya@gmail.com"],
// MoR's roster carries an 11-digit phone here; kept verbatim rather than "corrected".
["0088514835", "25191124368", "Taxpayer S", "ewnget77@gmail.com"],
["0076217301", "251960403750", "Taxpayer T", "merontamirat.redcloud@gmail.com"],
["0003826419", "251911516507", "Taxpayer 322", "alemayehu.t@marakisoft.com"],
["0056961577", "251929020729", "Taxpayer 323", "ltictsolution@gmail.com", undefined, "1234B"],
["0090853345", "251911376145", "Taxpayer 324", "kidusgoshu2be@gmail.com"],
["0000028643", "251988899003", "Taxpayer 325", "mesaysisay10@gmail.com"],
["0057751727", "251911437928", "Taxpayer 326", "zewdugeta@gmail.com"],
["0082549522", "251907256543", "Taxpayer 327", "brookgm2@gmail.com"],
["0093283311", "251953915419", "Taxpayer 328", "henock.ad@gmail.com"],
["0078795374", "251911091245", "Taxpayer 329", "danielltadesse@gmail.com"],
["0093346931", "251935724920", "Taxpayer 330", "halidabd63@gmail.com"],
["0040887091", "251913792959", "Taxpayer 331", "milextech@gmail.com"],
];
async function seedMorTestBuyers(): Promise<void> {
await AppDataSource.initialize();
try {
for (const [tin, phone, name, email, kebele, houseNo] of MOR_TEST_BUYERS) {
await AppDataSource.query(
`INSERT INTO freight.companies
(name, type, kind, status, tin, country, region, zone, woreda, kebele,
house_no, phone, email)
VALUES ($1, 'customer', 'commercial', 'active', $2, 'Ethiopia', 'Addis Ababa', 'Bole',
'NO WOREDA-144', $3, $4, $5, $6)
ON CONFLICT (tin) DO NOTHING`,
[name, tin, kebele ?? KEBELE, houseNo ?? HOUSE_NO, phone, email],
);
}
const summary = await AppDataSource.query(
`SELECT count(*)::int AS on_file,
count(*) FILTER (WHERE name LIKE 'Taxpayer %')::int AS seeded
FROM freight.companies
WHERE tin = ANY($1::text[])`,
[MOR_TEST_BUYERS.map(([tin]) => tin)],
);
console.table(summary);
console.log("Seeded MoR EIMS/BSP test buyers.");
} finally {
await AppDataSource.destroy();
}
}
seedMorTestBuyers().catch((err) => {
console.error("MoR test buyer seed failed:", err);
process.exit(1);
});