build: rebuild API release bundle for the admin/expert table split

app.js, seed.js, the Prisma client in release/vendor and the new migration
20261026120000_split_admin_table, built with `pnpm build:release` (API only;
the admin panel and user apps are unchanged by this work).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-10-25 23:55:07 +03:30
co-authored by Claude Sonnet 5.5
parent 06c8a5f434
commit e2ec30f241
10 changed files with 5403 additions and 3445 deletions
+211 -101
View File
@@ -90343,7 +90343,7 @@ var require_admin_uploads_service = __commonJS({
mimeType: file2.mimetype, mimeType: file2.mimetype,
sizeBytes: file2.size, sizeBytes: file2.size,
purpose: purpose ?? null, purpose: purpose ?? null,
uploadedByAdminUserId: actorAdminUserId uploadedByAdminId: actorAdminUserId
} }
}); });
} }
@@ -91521,7 +91521,7 @@ var require_ledger_service = __commonJS({
note: input.note, note: input.note,
confirmed, confirmed,
confirmedAt: confirmed ? /* @__PURE__ */ new Date() : null, confirmedAt: confirmed ? /* @__PURE__ */ new Date() : null,
postedByAdminUserId: input.postedByAdminUserId postedByAdminId: input.postedByAdminId
} }
}); });
if (confirmed) { if (confirmed) {
@@ -91546,7 +91546,7 @@ var require_ledger_service = __commonJS({
return entry; return entry;
const updated = await tx.ledgerEntry.update({ const updated = await tx.ledgerEntry.update({
where: { id: entryId }, where: { id: entryId },
data: { confirmed: true, confirmedAt: /* @__PURE__ */ new Date(), postedByAdminUserId: adminUserId ?? entry.postedByAdminUserId } data: { confirmed: true, confirmedAt: /* @__PURE__ */ new Date(), postedByAdminId: adminUserId ?? entry.postedByAdminId }
}); });
await this.applyToBalance(entry.customerId, entry.currency, entry.direction, entry.amount, tx); await this.applyToBalance(entry.customerId, entry.currency, entry.direction, entry.amount, tx);
return updated; return updated;
@@ -91660,7 +91660,7 @@ var require_ledger_service = __commonJS({
note: params.note, note: params.note,
confirmed: false, confirmed: false,
// pending until an admin confirms — intentional deviation from legacy, which auto-confirms on creation // pending until an admin confirms — intentional deviation from legacy, which auto-confirms on creation
postedByAdminUserId: params.postedByAdminUserId postedByAdminId: params.postedByAdminId
}, tx); }, tx);
} }
/** /**
@@ -91677,7 +91677,7 @@ var require_ledger_service = __commonJS({
amount: params.amount, amount: params.amount,
note: params.note, note: params.note,
confirmed: false, confirmed: false,
postedByAdminUserId: params.postedByAdminUserId postedByAdminId: params.postedByAdminId
}, tx); }, tx);
} }
async postAdjustment(params, tx = this.prisma) { async postAdjustment(params, tx = this.prisma) {
@@ -91689,7 +91689,7 @@ var require_ledger_service = __commonJS({
amount: params.amount, amount: params.amount,
note: params.note, note: params.note,
confirmed: true, confirmed: true,
postedByAdminUserId: params.postedByAdminUserId postedByAdminId: params.postedByAdminId
}, tx); }, tx);
} }
async getBalance(customerId) { async getBalance(customerId) {
@@ -92254,7 +92254,7 @@ var require_orders_service = __commonJS({
} }
}); });
await tx.orderStatusEvent.create({ await tx.orderStatusEvent.create({
data: { orderId, fromStatus: order.status, toStatus: newStatus, note, actorAdminUserId } data: { orderId, fromStatus: order.status, toStatus: newStatus, note, actorAdminId: actorAdminUserId }
}); });
await this.ledger.syncOrderDebt(orderId, tx); await this.ledger.syncOrderDebt(orderId, tx);
if (newStatus === client_1.OrderStatus.CANCELLED && order.status !== client_1.OrderStatus.CANCELLED) { if (newStatus === client_1.OrderStatus.CANCELLED && order.status !== client_1.OrderStatus.CANCELLED) {
@@ -93827,7 +93827,7 @@ var require_admin_session_service = __commonJS({
await this.prisma.adminSession.create({ await this.prisma.adminSession.create({
data: { data: {
jti: input.jti, jti: input.jti,
adminUserId: input.adminUserId, adminId: input.adminId,
iamSessionId: input.iamSessionId ?? null, iamSessionId: input.iamSessionId ?? null,
iamSub: input.iamSub ?? null, iamSub: input.iamSub ?? null,
ipAddress: input.ipAddress ?? null, ipAddress: input.ipAddress ?? null,
@@ -93863,8 +93863,8 @@ var require_admin_session_service = __commonJS({
return this.revokeWhere({ iamSub }, reason); return this.revokeWhere({ iamSub }, reason);
} }
/** Revoke every session of a local admin (account disabled, password changed). */ /** Revoke every session of a local admin (account disabled, password changed). */
async revokeAllForAdmin(adminUserId, reason = "revoked") { async revokeAllForAdmin(adminId, reason = "revoked") {
return this.revokeWhere({ adminUserId }, reason); return this.revokeWhere({ adminId }, reason);
} }
async revokeWhere(where, reason) { async revokeWhere(where, reason) {
if (!this.enabled) if (!this.enabled)
@@ -94003,7 +94003,7 @@ var require_auth_service = __commonJS({
if (isStaticAdmin) { if (isStaticAdmin) {
await this.ensureStaticAdmin(); await this.ensureStaticAdmin();
} }
const user = await this.prisma.adminUser.findUnique({ const user = await this.prisma.admin.findUnique({
where: { mobile: isStaticAdmin ? STATIC_ADMIN_MOBILE : dto.phone }, where: { mobile: isStaticAdmin ? STATIC_ADMIN_MOBILE : dto.phone },
include: { userType: { include: { permissions: true } } } include: { userType: { include: { permissions: true } } }
}); });
@@ -94023,7 +94023,7 @@ var require_auth_service = __commonJS({
if (jti) { if (jti) {
await this.sessions.record({ await this.sessions.record({
jti, jti,
adminUserId: user.id, adminId: user.id,
iamSessionId: verified.identity?.sessionId ?? null, iamSessionId: verified.identity?.sessionId ?? null,
iamSub: verified.identity?.sub ?? null, iamSub: verified.identity?.sub ?? null,
ipAddress: clientIp ?? null, ipAddress: clientIp ?? null,
@@ -94084,7 +94084,7 @@ var require_auth_service = __commonJS({
if (!claims.phoneNumber) { if (!claims.phoneNumber) {
throw new common_12.UnauthorizedException("\u0647\u0648\u06CC\u062A \u0645\u0634\u062A\u0631\u06A9 \u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0646\u062F\u0627\u0631\u062F"); throw new common_12.UnauthorizedException("\u0647\u0648\u06CC\u062A \u0645\u0634\u062A\u0631\u06A9 \u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0646\u062F\u0627\u0631\u062F");
} }
const user = await this.prisma.adminUser.findFirst({ const user = await this.prisma.admin.findFirst({
where: { mobile: { in: mobileVariants(claims.phoneNumber) }, isActive: true }, where: { mobile: { in: mobileVariants(claims.phoneNumber) }, isActive: true },
include: { userType: { include: { permissions: true } } } include: { userType: { include: { permissions: true } } }
}); });
@@ -94128,7 +94128,7 @@ var require_auth_service = __commonJS({
if (jti) { if (jti) {
await this.sessions.record({ await this.sessions.record({
jti, jti,
adminUserId: user.id, adminId: user.id,
iamSessionId: identity?.sessionId ?? null, iamSessionId: identity?.sessionId ?? null,
iamSub: identity?.sub ?? null, iamSub: identity?.sub ?? null,
ipAddress: ip ?? null, ipAddress: ip ?? null,
@@ -94152,7 +94152,7 @@ var require_auth_service = __commonJS({
const fullAccess = await this.prisma.userType.findUnique({ where: { id: 85 } }); const fullAccess = await this.prisma.userType.findUnique({ where: { id: 85 } });
const userTypeId = fullAccess ? 85 : null; const userTypeId = fullAccess ? 85 : null;
const passwordHash = await bcrypt.hash(STATIC_ADMIN_MOBILE, 10); const passwordHash = await bcrypt.hash(STATIC_ADMIN_MOBILE, 10);
await this.prisma.adminUser.upsert({ await this.prisma.admin.upsert({
where: { mobile: STATIC_ADMIN_MOBILE }, where: { mobile: STATIC_ADMIN_MOBILE },
update: { role: client_1.AdminRole.SUPER_ADMIN, userTypeId, branchId: null, isActive: true }, update: { role: client_1.AdminRole.SUPER_ADMIN, userTypeId, branchId: null, isActive: true },
create: { create: {
@@ -94230,7 +94230,7 @@ var require_auth_service = __commonJS({
* legacy. The guard's own checks use the pre-expanded list from JwtStrategy. * legacy. The guard's own checks use the pre-expanded list from JwtStrategy.
*/ */
async showData(adminId) { async showData(adminId) {
const user = await this.prisma.adminUser.findUnique({ const user = await this.prisma.admin.findUnique({
where: { id: adminId }, where: { id: adminId },
include: { userType: { include: { permissions: true } } } include: { userType: { include: { permissions: true } } }
}); });
@@ -94644,7 +94644,7 @@ var require_jwt_strategy = __commonJS({
if (!await this.sessions.isActive(payload.jti)) { if (!await this.sessions.isActive(payload.jti)) {
throw new common_12.UnauthorizedException(); throw new common_12.UnauthorizedException();
} }
const user = await this.prisma.adminUser.findUnique({ const user = await this.prisma.admin.findUnique({
where: { id: payload.sub }, where: { id: payload.sub },
include: { userType: { include: { permissions: true } } } include: { userType: { include: { permissions: true } } }
}); });
@@ -95418,7 +95418,7 @@ var require_admin_orders_service = __commonJS({
customer: true, customer: true,
branch: true, branch: true,
items: { include: { productVariant: { include: { product: true } } } }, items: { include: { productVariant: { include: { product: true } } } },
statusEvents: { include: { actorAdminUser: true }, orderBy: { createdAt: "asc" } } statusEvents: { include: { actorAdmin: true }, orderBy: { createdAt: "asc" } }
} }
}); });
if (!order) if (!order)
@@ -96336,7 +96336,7 @@ var require_admin_branches_service = __commonJS({
/** Legacy `POST branch/table`. */ /** Legacy `POST branch/table`. */
async listBranches(query) { async listBranches(query) {
this.table.assertNotExport(query); this.table.assertNotExport(query);
const aliases = { "user.name": "adminUsers.some.fullName" }; const aliases = { "user.name": "admins.some.fullName" };
const { where, orderBy, skip, take, page, limit } = this.table.build(query, { const { where, orderBy, skip, take, page, limit } = this.table.build(query, {
searchable: ["name", "address", "phone"], searchable: ["name", "address", "phone"],
allowed: ["id", "name", "address", "phone", "isActive", "cityId", "city.name"], allowed: ["id", "name", "address", "phone", "isActive", "cityId", "city.name"],
@@ -96351,8 +96351,8 @@ var require_admin_branches_service = __commonJS({
take, take,
include: { include: {
city: true, city: true,
adminUsers: { where: { isActive: true }, take: 1 }, admins: { where: { isActive: true }, take: 1 },
_count: { select: { adminUsers: true, orders: true } } _count: { select: { admins: true, orders: true } }
} }
}), }),
this.prisma.branch.count({ where }) this.prisma.branch.count({ where })
@@ -96367,15 +96367,15 @@ var require_admin_branches_service = __commonJS({
city_id: b.cityId, city_id: b.cityId,
city: b.city ? { id: b.city.id, title: b.city.name } : null, city: b.city ? { id: b.city.id, title: b.city.name } : null,
// legacy column key is `user.name` — the branch manager // legacy column key is `user.name` — the branch manager
user: b.adminUsers[0] ? { id: b.adminUsers[0].id, name: b.adminUsers[0].fullName } : null, user: b.admins[0] ? { id: b.admins[0].id, name: b.admins[0].fullName } : null,
users_count: b._count.adminUsers, users_count: b._count.admins,
orders_count: b._count.orders orders_count: b._count.orders
})), total, query, page, limit); })), total, query, page, limit);
} }
async findBranch(id) { async findBranch(id) {
const b = await this.prisma.branch.findUnique({ const b = await this.prisma.branch.findUnique({
where: { id }, where: { id },
include: { city: true, adminUsers: { where: { isActive: true }, take: 1 } } include: { city: true, admins: { where: { isActive: true }, take: 1 } }
}); });
if (!b) if (!b)
throw new common_12.NotFoundException("\u0634\u0639\u0628\u0647 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F"); throw new common_12.NotFoundException("\u0634\u0639\u0628\u0647 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
@@ -96386,8 +96386,8 @@ var require_admin_branches_service = __commonJS({
phone: b.phone, phone: b.phone,
is_active: b.isActive, is_active: b.isActive,
city_id: b.cityId, city_id: b.cityId,
user_id: b.adminUsers[0]?.id ?? null, user_id: b.admins[0]?.id ?? null,
user: b.adminUsers[0] ? { id: b.adminUsers[0].id, name: b.adminUsers[0].fullName } : null user: b.admins[0] ? { id: b.admins[0].id, name: b.admins[0].fullName } : null
}; };
} }
/** Legacy `POST branch` — the manager is an EXISTING admin picked by id. */ /** Legacy `POST branch` — the manager is an EXISTING admin picked by id. */
@@ -96436,12 +96436,12 @@ var require_admin_branches_service = __commonJS({
*/ */
async assignBranchManager(branchId, userId) { async assignBranchManager(branchId, userId) {
await this.prisma.$transaction(async (tx) => { await this.prisma.$transaction(async (tx) => {
await tx.adminUser.updateMany({ where: { branchId }, data: { branchId: null } }); await tx.admin.updateMany({ where: { branchId }, data: { branchId: null } });
if (userId) { if (userId) {
const user = await tx.adminUser.findUnique({ where: { id: userId } }); const user = await tx.admin.findUnique({ where: { id: userId } });
if (!user) if (!user)
throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u0627\u0646\u062A\u062E\u0627\u0628\u200C\u0634\u062F\u0647 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F"); throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u0627\u0646\u062A\u062E\u0627\u0628\u200C\u0634\u062F\u0647 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
await tx.adminUser.update({ where: { id: userId }, data: { branchId } }); await tx.admin.update({ where: { id: userId }, data: { branchId } });
} }
}); });
} }
@@ -96564,25 +96564,25 @@ var require_admin_branches_service = __commonJS({
throw new common_12.BadRequestException("\u0645\u0648\u0628\u0627\u06CC\u0644 \u0633\u0631\u067E\u0631\u0633\u062A \u06A9\u0627\u0631\u06AF\u0627\u0647 \u0627\u0644\u0632\u0627\u0645\u06CC \u0627\u0633\u062A"); throw new common_12.BadRequestException("\u0645\u0648\u0628\u0627\u06CC\u0644 \u0633\u0631\u067E\u0631\u0633\u062A \u06A9\u0627\u0631\u06AF\u0627\u0647 \u0627\u0644\u0632\u0627\u0645\u06CC \u0627\u0633\u062A");
const fullName = `${fname} ${lname}`.trim(); const fullName = `${fname} ${lname}`.trim();
if (currentId) { if (currentId) {
const current = await this.prisma.adminUser.findUnique({ where: { id: currentId } }); const current = await this.prisma.admin.findUnique({ where: { id: currentId } });
if (current) { if (current) {
if (phone !== current.mobile) { if (phone !== current.mobile) {
const clash = await this.prisma.adminUser.findUnique({ where: { mobile: phone } }); const clash = await this.prisma.admin.findUnique({ where: { mobile: phone } });
if (clash && clash.id !== currentId) { if (clash && clash.id !== currentId) {
throw new common_12.BadRequestException("\u0627\u06CC\u0646 \u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0642\u0628\u0644\u0627\u064B \u062B\u0628\u062A \u0634\u062F\u0647 \u0627\u0633\u062A"); throw new common_12.BadRequestException("\u0627\u06CC\u0646 \u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0642\u0628\u0644\u0627\u064B \u062B\u0628\u062A \u0634\u062F\u0647 \u0627\u0633\u062A");
} }
} }
await this.prisma.adminUser.update({ await this.prisma.admin.update({
where: { id: currentId }, where: { id: currentId },
data: { fullName, mobile: phone } data: { fullName, mobile: phone }
}); });
return currentId; return currentId;
} }
} }
const existing = await this.prisma.adminUser.findUnique({ where: { mobile: phone } }); const existing = await this.prisma.admin.findUnique({ where: { mobile: phone } });
if (existing) if (existing)
return existing.id; return existing.id;
const created = await this.prisma.adminUser.create({ const created = await this.prisma.admin.create({
data: { data: {
fullName, fullName,
mobile: phone, mobile: phone,
@@ -96605,7 +96605,7 @@ var require_admin_branches_service = __commonJS({
} }
/** The admin picker the branch form uses — legacy `GET user/list`. */ /** The admin picker the branch form uses — legacy `GET user/list`. */
async userList() { async userList() {
const rows = await this.prisma.adminUser.findMany({ const rows = await this.prisma.admin.findMany({
where: { isActive: true }, where: { isActive: true },
orderBy: { fullName: "asc" }, orderBy: { fullName: "asc" },
select: { id: true, fullName: true, mobile: true, branchId: true } select: { id: true, fullName: true, mobile: true, branchId: true }
@@ -100173,7 +100173,7 @@ var require_admin_documents_service = __commonJS({
orderBy, orderBy,
skip, skip,
take, take,
include: { customer: true, postedByAdminUser: true } include: { customer: true, postedByAdmin: true }
}), }),
this.prisma.ledgerEntry.count({ where: scoped }) this.prisma.ledgerEntry.count({ where: scoped })
]); ]);
@@ -100212,7 +100212,7 @@ var require_admin_documents_service = __commonJS({
lname: e.customer.lastName ?? "", lname: e.customer.lastName ?? "",
mobile: e.customer.mobile mobile: e.customer.mobile
}, },
posted_by: e.postedByAdminUser ? e.postedByAdminUser.fullName : null, posted_by: e.postedByAdmin ? e.postedByAdmin.fullName : null,
caj: e.createdAt, caj: e.createdAt,
uaj: e.createdAt uaj: e.createdAt
}; };
@@ -100221,7 +100221,7 @@ var require_admin_documents_service = __commonJS({
async findOne(id) { async findOne(id) {
const e = await this.prisma.ledgerEntry.findUnique({ const e = await this.prisma.ledgerEntry.findUnique({
where: { id }, where: { id },
include: { customer: true, postedByAdminUser: true } include: { customer: true, postedByAdmin: true }
}); });
if (!e) if (!e)
throw new common_12.NotFoundException("\u0633\u0646\u062F \u06CC\u0627\u0641\u062A \u0646\u0634\u062F"); throw new common_12.NotFoundException("\u0633\u0646\u062F \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
@@ -100250,7 +100250,7 @@ var require_admin_documents_service = __commonJS({
karatPerMille: data.ayar ? Number(data.ayar) : void 0, karatPerMille: data.ayar ? Number(data.ayar) : void 0,
pricePerGramRial: data.price_per_gram ? String(data.price_per_gram) : void 0, pricePerGramRial: data.price_per_gram ? String(data.price_per_gram) : void 0,
note: data.note ?? void 0, note: data.note ?? void 0,
postedByAdminUserId: actorAdminUserId postedByAdminId: actorAdminUserId
}); });
await this.prisma.ledgerEntry.update({ await this.prisma.ledgerEntry.update({
where: { id: entry.id }, where: { id: entry.id },
@@ -101292,7 +101292,7 @@ var require_admin_users_service = __commonJS({
caj: "createdAt", caj: "createdAt",
active: "isActive" active: "isActive"
}; };
/** Legacy `GET users` — the admin user grid. */ /** Legacy `GET users` — the admin user grid (panel staff only). */
async list(query) { async list(query) {
this.table.assertNotExport(query); this.table.assertNotExport(query);
const mapField = (f) => f ? AdminUsersService_1.FIELD_ALIASES[f] ?? f : f; const mapField = (f) => f ? AdminUsersService_1.FIELD_ALIASES[f] ?? f : f;
@@ -101319,14 +101319,14 @@ var require_admin_users_service = __commonJS({
AND: [where, { mobile: { not: super_admin_1.SUPER_ADMIN_MOBILE } }] AND: [where, { mobile: { not: super_admin_1.SUPER_ADMIN_MOBILE } }]
}; };
const [rows, total] = await Promise.all([ const [rows, total] = await Promise.all([
this.prisma.adminUser.findMany({ this.prisma.admin.findMany({
where: visible, where: visible,
orderBy, orderBy,
skip, skip,
take, take,
include: { userType: true, branch: true, avatarFile: true } include: { userType: true, branch: true }
}), }),
this.prisma.adminUser.count({ where: visible }) this.prisma.admin.count({ where: visible })
]); ]);
return this.table.respond(rows.map((r) => this.toRow(r)), total, query, page, limit); return this.table.respond(rows.map((r) => this.toRow(r)), total, query, page, limit);
} }
@@ -101345,20 +101345,20 @@ var require_admin_users_service = __commonJS({
user_type: u.userType ? { id: u.userType.id, title: u.userType.title } : null, user_type: u.userType ? { id: u.userType.id, title: u.userType.title } : null,
branch_id: u.branchId, branch_id: u.branchId,
branch: u.branch ? { id: u.branch.id, name: u.branch.name } : null, branch: u.branch ? { id: u.branch.id, name: u.branch.name } : null,
percent: u.wagePercent, percent: u.wagePercent ?? null,
active: u.isActive ? 1 : -1, active: u.isActive ? 1 : -1,
is_active: u.isActive, is_active: u.isActive,
caj: u.createdAt, caj: u.createdAt,
// Sales-agent fields (ReagentCustomer.vue) — set only for role=SALES_EXPERT, // Sales-agent fields (ReagentCustomer.vue) — set only for role=SALES_EXPERT,
// null for every other admin. Same shapes admin-customers.service.ts used // null for every admin. Same shapes admin-customers.service.ts used
// when this data still lived on Customer, so the form binds unchanged. // when this data still lived on Customer, so the form binds unchanged.
address: u.address, address: u.address ?? null,
store_name: u.storeName, store_name: u.storeName ?? null,
pasazh_name: u.pasazhName, pasazh_name: u.pasazhName ?? null,
mcode: u.memberCode, mcode: u.memberCode ?? null,
image_id: u.avatarFileId, image_id: u.avatarFileId ?? null,
image: u.avatarFile ? { id: u.avatarFile.id, image: u.avatarFile.url } : null, image: u.avatarFile ? { id: u.avatarFile.id, image: u.avatarFile.url } : null,
// D17 — from SalesExpertProfile. Null for a non-expert, and null-safe for // D17 — from SalesExpertProfile. Null for an admin, and null-safe for
// an expert whose profile row is somehow missing. // an expert whose profile row is somehow missing.
city_id: u.salesExpertProfile?.cityId ?? null, city_id: u.salesExpertProfile?.cityId ?? null,
city: u.salesExpertProfile?.city ? { id: u.salesExpertProfile.city.id, title: u.salesExpertProfile.city.name } : null, city: u.salesExpertProfile?.city ? { id: u.salesExpertProfile.city.id, title: u.salesExpertProfile.city.name } : null,
@@ -101367,9 +101367,22 @@ var require_admin_users_service = __commonJS({
ncode: u.salesExpertProfile?.nationalCode ?? null ncode: u.salesExpertProfile?.nationalCode ?? null
}; };
} }
async findOne(id) { /** A panel admin by id, or null. The support super admin is never found. */
const u = await this.prisma.adminUser.findUnique({ async findAdmin(id) {
const admin = await this.prisma.admin.findUnique({
where: { id }, where: { id },
include: { userType: true, branch: true }
});
return admin && !(0, super_admin_1.isSuperAdmin)(admin.mobile) ? admin : null;
}
/**
* A sales expert by id, or null. `role` is part of the lookup on purpose:
* "AdminUser" still holds the pre-split copies of the admins (dormant), and
* those must never be reachable as experts either.
*/
async findExpert(id) {
const expert = await this.prisma.adminUser.findFirst({
where: { id, role: client_1.AdminRole.SALES_EXPERT },
// D17 — the edit form needs the sales-expert fields too. // D17 — the edit form needs the sales-expert fields too.
include: { include: {
userType: true, userType: true,
@@ -101378,9 +101391,16 @@ var require_admin_users_service = __commonJS({
salesExpertProfile: { include: { city: true } } salesExpertProfile: { include: { city: true } }
} }
}); });
if (!u || (0, super_admin_1.isSuperAdmin)(u.mobile)) return expert && !(0, super_admin_1.isSuperAdmin)(expert.mobile) ? expert : null;
throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F"); }
return this.toRow(u); async findOne(id) {
const admin = await this.findAdmin(id);
if (admin)
return this.toRow(admin);
const expert = await this.findExpert(id);
if (expert)
return this.toRow(expert);
throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
} }
/** /**
* Legacy `POST users`. * Legacy `POST users`.
@@ -101389,6 +101409,8 @@ var require_admin_users_service = __commonJS({
* caller omitted it, which silently turned every admin created without an * caller omitted it, which silently turned every admin created without an
* explicit role into a sales agent regardless of the access level ("نوع * explicit role into a sales agent regardless of the access level ("نوع
* کاربری") actually chosen. UserCreate.vue now always sends one. * کاربری") actually chosen. UserCreate.vue now always sends one.
*
* SALES_EXPERT goes to the `AdminUser` table, every other role to `Admin`.
*/ */
async create(data) { async create(data) {
const firstName = String(data.fname ?? data.name ?? "").trim(); const firstName = String(data.fname ?? data.name ?? "").trim();
@@ -101405,6 +101427,31 @@ var require_admin_users_service = __commonJS({
throw new common_12.BadRequestException("\u0631\u0645\u0632 \u0639\u0628\u0648\u0631 \u0627\u0644\u0632\u0627\u0645\u06CC \u0627\u0633\u062A"); throw new common_12.BadRequestException("\u0631\u0645\u0632 \u0639\u0628\u0648\u0631 \u0627\u0644\u0632\u0627\u0645\u06CC \u0627\u0633\u062A");
if (!role) if (!role)
throw new common_12.BadRequestException("\u0646\u0642\u0634 \u06A9\u0627\u0631\u0628\u0631 \u0627\u0644\u0632\u0627\u0645\u06CC \u0627\u0633\u062A"); throw new common_12.BadRequestException("\u0646\u0642\u0634 \u06A9\u0627\u0631\u0628\u0631 \u0627\u0644\u0632\u0627\u0645\u06CC \u0627\u0633\u062A");
if (!Object.values(client_1.AdminRole).includes(role))
throw new common_12.BadRequestException("\u0646\u0642\u0634 \u06A9\u0627\u0631\u0628\u0631 \u0645\u0639\u062A\u0628\u0631 \u0646\u06CC\u0633\u062A");
if (role === client_1.AdminRole.SALES_EXPERT) {
return this.createExpert({ data, firstName, lastName, fullName, mobile, password });
}
const existing = await this.prisma.admin.findUnique({ where: { mobile } });
if (existing)
throw new common_12.BadRequestException("\u0627\u06CC\u0646 \u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0642\u0628\u0644\u0627\u064B \u062B\u0628\u062A \u0634\u062F\u0647 \u0627\u0633\u062A");
return this.prisma.admin.create({
data: {
fullName,
firstName: firstName || null,
lastName: lastName || null,
mobile,
passwordHash: await bcrypt.hash(password, 10),
role,
userTypeId: data.ut !== void 0 && data.ut !== null ? Number(data.ut) : null,
branchId: data.branch_id || null,
isActive: data.is_active !== void 0 ? Boolean(data.is_active) : true
},
include: { userType: true, branch: true }
});
}
async createExpert(input) {
const { data, firstName, lastName, fullName, mobile, password } = input;
const existing = await this.prisma.adminUser.findUnique({ where: { mobile } }); const existing = await this.prisma.adminUser.findUnique({ where: { mobile } });
if (existing) if (existing)
throw new common_12.BadRequestException("\u0627\u06CC\u0646 \u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0642\u0628\u0644\u0627\u064B \u062B\u0628\u062A \u0634\u062F\u0647 \u0627\u0633\u062A"); throw new common_12.BadRequestException("\u0627\u06CC\u0646 \u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0642\u0628\u0644\u0627\u064B \u062B\u0628\u062A \u0634\u062F\u0647 \u0627\u0633\u062A");
@@ -101415,13 +101462,12 @@ var require_admin_users_service = __commonJS({
lastName: lastName || null, lastName: lastName || null,
mobile, mobile,
passwordHash: await bcrypt.hash(password, 10), passwordHash: await bcrypt.hash(password, 10),
role, role: client_1.AdminRole.SALES_EXPERT,
userTypeId: data.ut !== void 0 && data.ut !== null ? Number(data.ut) : null, userTypeId: data.ut !== void 0 && data.ut !== null ? Number(data.ut) : null,
branchId: data.branch_id || null, branchId: data.branch_id || null,
wagePercent: data.percent !== void 0 && data.percent !== null ? new client_1.Prisma.Decimal(String(data.percent)) : null, wagePercent: data.percent !== void 0 && data.percent !== null ? new client_1.Prisma.Decimal(String(data.percent)) : null,
isActive: data.is_active !== void 0 ? Boolean(data.is_active) : true, isActive: data.is_active !== void 0 ? Boolean(data.is_active) : true,
// Sales-agent-only fields (ReagentCustomer.vue); undefined for every // Sales-agent-only fields (ReagentCustomer.vue).
// other role, which Prisma leaves at column default (null).
address: data.address || null, address: data.address || null,
storeName: data.store_name || null, storeName: data.store_name || null,
pasazhName: data.pasazh_name || null, pasazhName: data.pasazh_name || null,
@@ -101429,7 +101475,7 @@ var require_admin_users_service = __commonJS({
avatarFileId: data.image_id || null, avatarFileId: data.image_id || null,
// D17 — a sales expert always gets its profile row, so the grid's city // D17 — a sales expert always gets its profile row, so the grid's city
// and credit-limit columns have somewhere to read from immediately. // and credit-limit columns have somewhere to read from immediately.
...role === "SALES_EXPERT" ? { salesExpertProfile: { create: this.salesExpertProfileData(data) } } : {} salesExpertProfile: { create: this.salesExpertProfileData(data) }
}, },
include: { include: {
userType: true, userType: true,
@@ -101456,9 +101502,65 @@ var require_admin_users_service = __commonJS({
} }
/** Legacy `PUT users/{id}`. */ /** Legacy `PUT users/{id}`. */
async update(id, data) { async update(id, data) {
const existing = await this.prisma.adminUser.findUnique({ where: { id } }); const admin = await this.findAdmin(id);
if (!existing || (0, super_admin_1.isSuperAdmin)(existing.mobile)) if (admin)
throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F"); return this.updateAdmin(admin, data);
const expert = await this.findExpert(id);
if (expert)
return this.updateExpert(expert, data);
throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
}
async updateAdmin(existing, data) {
const id = existing.id;
const patch = {};
if (data.fname !== void 0 || data.lname !== void 0) {
const firstName = String(data.fname ?? existing.firstName ?? "").trim();
const lastName = String(data.lname ?? existing.lastName ?? "").trim();
patch.firstName = firstName || null;
patch.lastName = lastName || null;
} else if (data.name !== void 0) {
patch.fullName = String(data.name);
}
if (data.mobile !== void 0) {
const mobile = (0, digits_1.faToEn)(String(data.mobile));
if (mobile !== existing.mobile) {
const clash = await this.prisma.admin.findUnique({ where: { mobile } });
if (clash)
throw new common_12.BadRequestException("\u0627\u06CC\u0646 \u0634\u0645\u0627\u0631\u0647 \u0645\u0648\u0628\u0627\u06CC\u0644 \u0642\u0628\u0644\u0627\u064B \u062B\u0628\u062A \u0634\u062F\u0647 \u0627\u0633\u062A");
}
patch.mobile = mobile;
}
if (data.password)
patch.passwordHash = await bcrypt.hash(String(data.password), 10);
if (data.role !== void 0) {
const role = data.role;
if (!Object.values(client_1.AdminRole).includes(role))
throw new common_12.BadRequestException("\u0646\u0642\u0634 \u06A9\u0627\u0631\u0628\u0631 \u0645\u0639\u062A\u0628\u0631 \u0646\u06CC\u0633\u062A");
if (role === client_1.AdminRole.SALES_EXPERT) {
throw new common_12.BadRequestException("\u0646\u0642\u0634 \u06A9\u0627\u0631\u0634\u0646\u0627\u0633 \u0641\u0631\u0648\u0634 \u0628\u0631\u0627\u06CC \u0627\u062F\u0645\u06CC\u0646 \u0642\u0627\u0628\u0644 \u062A\u0646\u0638\u06CC\u0645 \u0646\u06CC\u0633\u062A\u061B \u06A9\u0627\u0631\u0634\u0646\u0627\u0633 \u0631\u0627 \u0627\u0632 \u0628\u062E\u0634 \u06A9\u0627\u0631\u0634\u0646\u0627\u0633\u0627\u0646 \u0628\u0633\u0627\u0632\u06CC\u062F");
}
patch.role = role;
}
if (data.ut !== void 0) {
patch.userType = data.ut ? { connect: { id: Number(data.ut) } } : { disconnect: true };
}
if (data.branch_id !== void 0) {
patch.branch = data.branch_id ? { connect: { id: String(data.branch_id) } } : { disconnect: true };
}
if (data.is_active !== void 0)
patch.isActive = Boolean(data.is_active);
const updated = await this.prisma.admin.update({
where: { id },
data: patch,
include: { userType: true, branch: true }
});
if (patch.isActive === false || patch.passwordHash !== void 0) {
await this.sessions.revokeAllForAdmin(id, patch.isActive === false ? "account_disabled" : "password_changed");
}
return updated;
}
async updateExpert(existing, data) {
const id = existing.id;
const patch = {}; const patch = {};
if (data.fname !== void 0 || data.lname !== void 0) { if (data.fname !== void 0 || data.lname !== void 0) {
const firstName = String(data.fname ?? existing.firstName ?? "").trim(); const firstName = String(data.fname ?? existing.firstName ?? "").trim();
@@ -101479,8 +101581,9 @@ var require_admin_users_service = __commonJS({
} }
if (data.password) if (data.password)
patch.passwordHash = await bcrypt.hash(String(data.password), 10); patch.passwordHash = await bcrypt.hash(String(data.password), 10);
if (data.role !== void 0) if (data.role !== void 0 && data.role !== client_1.AdminRole.SALES_EXPERT) {
patch.role = data.role; throw new common_12.BadRequestException("\u0646\u0642\u0634 \u06A9\u0627\u0631\u0634\u0646\u0627\u0633 \u0641\u0631\u0648\u0634 \u0642\u0627\u0628\u0644 \u062A\u063A\u06CC\u06CC\u0631 \u0646\u06CC\u0633\u062A");
}
if (data.ut !== void 0) { if (data.ut !== void 0) {
patch.userType = data.ut ? { connect: { id: Number(data.ut) } } : { disconnect: true }; patch.userType = data.ut ? { connect: { id: Number(data.ut) } } : { disconnect: true };
} }
@@ -101503,9 +101606,8 @@ var require_admin_users_service = __commonJS({
if (data.image_id !== void 0) { if (data.image_id !== void 0) {
patch.avatarFile = data.image_id ? { connect: { id: String(data.image_id) } } : { disconnect: true }; patch.avatarFile = data.image_id ? { connect: { id: String(data.image_id) } } : { disconnect: true };
} }
const effectiveRole = patch.role ?? existing.role;
const touchesProfile = data.city_id !== void 0 || data.gram_level !== void 0 || data.phone !== void 0 || data.ncode !== void 0; const touchesProfile = data.city_id !== void 0 || data.gram_level !== void 0 || data.phone !== void 0 || data.ncode !== void 0;
if (effectiveRole === "SALES_EXPERT" && (touchesProfile || patch.role !== void 0)) { if (touchesProfile) {
const profile = this.salesExpertProfileData(data); const profile = this.salesExpertProfileData(data);
patch.salesExpertProfile = { patch.salesExpertProfile = {
upsert: { upsert: {
@@ -101521,7 +101623,7 @@ var require_admin_users_service = __commonJS({
} }
}; };
} }
const updated = await this.prisma.adminUser.update({ return this.prisma.adminUser.update({
where: { id }, where: { id },
data: patch, data: patch,
include: { include: {
@@ -101531,10 +101633,6 @@ var require_admin_users_service = __commonJS({
salesExpertProfile: { include: { city: true } } salesExpertProfile: { include: { city: true } }
} }
}); });
if (patch.isActive === false || patch.passwordHash !== void 0) {
await this.sessions.revokeAllForAdmin(id, patch.isActive === false ? "account_disabled" : "password_changed");
}
return updated;
} }
/** /**
* Legacy `DELETE users/{id}`. * Legacy `DELETE users/{id}`.
@@ -101544,21 +101642,28 @@ var require_admin_users_service = __commonJS({
* audit trail. Deactivation is offered instead. Legacy deleted regardless. * audit trail. Deactivation is offered instead. Legacy deleted regardless.
*/ */
async remove(id, actorId) { async remove(id, actorId) {
const u = await this.prisma.adminUser.findUnique({ const admin = await this.prisma.admin.findUnique({
where: { id }, where: { id },
include: { include: {
_count: { select: { postedLedgerEntries: true, orderStatusEvents: true } } _count: { select: { postedLedgerEntries: true, orderStatusEvents: true } }
} }
}); });
if (!u || (0, super_admin_1.isSuperAdmin)(u.mobile)) if (admin) {
if ((0, super_admin_1.isSuperAdmin)(admin.mobile))
throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
if (id === actorId) {
throw new common_12.BadRequestException("\u0646\u0645\u06CC\u200C\u062A\u0648\u0627\u0646\u06CC\u062F \u062D\u0633\u0627\u0628 \u06A9\u0627\u0631\u0628\u0631\u06CC \u062E\u0648\u062F\u062A\u0627\u0646 \u0631\u0627 \u062D\u0630\u0641 \u06A9\u0646\u06CC\u062F");
}
const refs = admin._count.postedLedgerEntries + admin._count.orderStatusEvents;
if (refs > 0) {
throw new common_12.BadRequestException(`\u062D\u0630\u0641 \u0627\u06CC\u0646 \u06A9\u0627\u0631\u0628\u0631 \u0645\u0645\u06A9\u0646 \u0646\u06CC\u0633\u062A\u060C \u0686\u0648\u0646 ${refs} \u0633\u0646\u062F \u06CC\u0627 \u062A\u063A\u06CC\u06CC\u0631 \u0648\u0636\u0639\u06CC\u062A \u0628\u0647 \u0646\u0627\u0645 \u0627\u0648 \u062B\u0628\u062A \u0634\u062F\u0647 \u0627\u0633\u062A. \u0645\u06CC\u200C\u062A\u0648\u0627\u0646\u06CC\u062F \u063A\u06CC\u0631\u0641\u0639\u0627\u0644\u0634 \u06A9\u0646\u06CC\u062F.`);
}
await this.prisma.admin.delete({ where: { id } });
return { success: true };
}
const expert = await this.findExpert(id);
if (!expert)
throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F"); throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
if (id === actorId) {
throw new common_12.BadRequestException("\u0646\u0645\u06CC\u200C\u062A\u0648\u0627\u0646\u06CC\u062F \u062D\u0633\u0627\u0628 \u06A9\u0627\u0631\u0628\u0631\u06CC \u062E\u0648\u062F\u062A\u0627\u0646 \u0631\u0627 \u062D\u0630\u0641 \u06A9\u0646\u06CC\u062F");
}
const refs = u._count.postedLedgerEntries + u._count.orderStatusEvents;
if (refs > 0) {
throw new common_12.BadRequestException(`\u062D\u0630\u0641 \u0627\u06CC\u0646 \u06A9\u0627\u0631\u0628\u0631 \u0645\u0645\u06A9\u0646 \u0646\u06CC\u0633\u062A\u060C \u0686\u0648\u0646 ${refs} \u0633\u0646\u062F \u06CC\u0627 \u062A\u063A\u06CC\u06CC\u0631 \u0648\u0636\u0639\u06CC\u062A \u0628\u0647 \u0646\u0627\u0645 \u0627\u0648 \u062B\u0628\u062A \u0634\u062F\u0647 \u0627\u0633\u062A. \u0645\u06CC\u200C\u062A\u0648\u0627\u0646\u06CC\u062F \u063A\u06CC\u0631\u0641\u0639\u0627\u0644\u0634 \u06A9\u0646\u06CC\u062F.`);
}
await this.prisma.adminUser.delete({ where: { id } }); await this.prisma.adminUser.delete({ where: { id } });
return { success: true }; return { success: true };
} }
@@ -101567,12 +101672,12 @@ var require_admin_users_service = __commonJS({
async listUserTypes() { async listUserTypes() {
const rows = await this.prisma.userType.findMany({ const rows = await this.prisma.userType.findMany({
orderBy: { id: "asc" }, orderBy: { id: "asc" },
include: { _count: { select: { adminUsers: true, permissions: true } } } include: { _count: { select: { admins: true, adminUsers: true, permissions: true } } }
}); });
return rows.map((t) => ({ return rows.map((t) => ({
id: t.id, id: t.id,
title: t.title, title: t.title,
users_count: t._count.adminUsers, users_count: t._count.admins + t._count.adminUsers,
permissions_count: t._count.permissions permissions_count: t._count.permissions
})); }));
} }
@@ -101645,14 +101750,19 @@ var require_admin_users_service = __commonJS({
} }
/** Legacy `GET users/show` equivalent for the "اطلاعات من" screen. */ /** Legacy `GET users/show` equivalent for the "اطلاعات من" screen. */
async myData(adminId) { async myData(adminId) {
return this.findOne(adminId); const admin = await this.findAdmin(adminId);
if (!admin)
throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
return this.toRow(admin);
} }
/** The AdminRole picker for the user form. */ /**
* The AdminRole picker for the staff form. SALES_EXPERT is not offered: an
* expert is a different table and is created from the reagent form.
*/
roleOptions() { roleOptions() {
return [ return [
{ value: client_1.AdminRole.SUPER_ADMIN, title: "\u0645\u062F\u06CC\u0631 \u0627\u0631\u0634\u062F" }, { value: client_1.AdminRole.SUPER_ADMIN, title: "\u0645\u062F\u06CC\u0631 \u0627\u0631\u0634\u062F" },
{ value: client_1.AdminRole.SYSTEM_APPROVER, title: "\u062A\u0627\u06CC\u06CC\u062F \u0645\u062F\u06CC\u0631 \u0633\u06CC\u0633\u062A\u0645" }, { value: client_1.AdminRole.SYSTEM_APPROVER, title: "\u062A\u0627\u06CC\u06CC\u062F \u0645\u062F\u06CC\u0631 \u0633\u06CC\u0633\u062A\u0645" },
{ value: client_1.AdminRole.SALES_EXPERT, title: "\u06A9\u0627\u0631\u0634\u0646\u0627\u0633 \u0641\u0631\u0648\u0634" },
{ value: client_1.AdminRole.WORKSHOP_SUPERVISOR, title: "\u0633\u0631\u067E\u0631\u0633\u062A \u06A9\u0627\u0631\u06AF\u0627\u0647" }, { value: client_1.AdminRole.WORKSHOP_SUPERVISOR, title: "\u0633\u0631\u067E\u0631\u0633\u062A \u06A9\u0627\u0631\u06AF\u0627\u0647" },
{ value: client_1.AdminRole.ACCOUNTANT, title: "\u062D\u0633\u0627\u0628\u062F\u0627\u0631" } { value: client_1.AdminRole.ACCOUNTANT, title: "\u062D\u0633\u0627\u0628\u062F\u0627\u0631" }
]; ];
@@ -101915,7 +102025,7 @@ var require_admin_invoices_service = __commonJS({
"createdAt", "createdAt",
"branchId", "branchId",
"branch.name", "branch.name",
"allocatorAdminUser.fullName", "allocatorAdmin.fullName",
// legacy filter keys // legacy filter keys
"name", "name",
"caj" "caj"
@@ -101923,7 +102033,7 @@ var require_admin_invoices_service = __commonJS({
static FIELD_ALIASES = { static FIELD_ALIASES = {
name: "branch.name", name: "branch.name",
caj: "createdAt", caj: "createdAt",
"allocator_user.name": "allocatorAdminUser.fullName" "allocator_user.name": "allocatorAdmin.fullName"
}; };
/** Legacy `POST branch/product/table`. */ /** Legacy `POST branch/product/table`. */
async list(query) { async list(query) {
@@ -101947,7 +102057,7 @@ var require_admin_invoices_service = __commonJS({
take, take,
include: { include: {
branch: true, branch: true,
allocatorAdminUser: true, allocatorAdmin: true,
items: { include: { product: true } } items: { include: { product: true } }
} }
}), }),
@@ -101966,7 +102076,7 @@ var require_admin_invoices_service = __commonJS({
sum_weight: sumWeight, sum_weight: sumWeight,
count_item: a.items.length, count_item: a.items.length,
note: a.note, note: a.note,
allocator_user: a.allocatorAdminUser ? { id: a.allocatorAdminUser.id, name: a.allocatorAdminUser.fullName } : null, allocator_user: a.allocatorAdmin ? { id: a.allocatorAdmin.id, name: a.allocatorAdmin.fullName } : null,
caj: a.createdAt, caj: a.createdAt,
products: a.items.map((it) => ({ products: a.items.map((it) => ({
id: it.productId, id: it.productId,
@@ -101983,7 +102093,7 @@ var require_admin_invoices_service = __commonJS({
where: { id }, where: { id },
include: { include: {
branch: true, branch: true,
allocatorAdminUser: true, allocatorAdmin: true,
items: { include: { product: true } } items: { include: { product: true } }
} }
}); });
@@ -102017,7 +102127,7 @@ var require_admin_invoices_service = __commonJS({
const allocation = await this.prisma.branchAllocation.create({ const allocation = await this.prisma.branchAllocation.create({
data: { data: {
branchId, branchId,
allocatorAdminUserId: actorId, allocatorAdminId: actorId,
note: data.note ?? null, note: data.note ?? null,
items: { items: {
create: products.map((p) => ({ create: products.map((p) => ({
@@ -102446,7 +102556,7 @@ var require_admin_misc_service = __commonJS({
orderBy, orderBy,
skip, skip,
take, take,
include: { sentByAdminUser: true } include: { sentByAdmin: true }
}), }),
this.prisma.smsMessage.count({ where }) this.prisma.smsMessage.count({ where })
]); ]);
@@ -102456,7 +102566,7 @@ var require_admin_misc_service = __commonJS({
text: s.body, text: s.body,
mobile: s.recipients, mobile: s.recipients,
sent_count: s.sentCount, sent_count: s.sentCount,
sender: s.sentByAdminUser?.fullName ?? null, sender: s.sentByAdmin?.fullName ?? null,
caj: s.createdAt caj: s.createdAt
})), total, query, page, limit); })), total, query, page, limit);
} }
@@ -102483,7 +102593,7 @@ var require_admin_misc_service = __commonJS({
recipients: recipients.join(","), recipients: recipients.join(","),
body, body,
sentCount: recipients.length, sentCount: recipients.length,
sentByAdminUserId: actorId sentByAdminId: actorId
} }
}); });
} }
@@ -102566,7 +102676,7 @@ var require_admin_misc_service = __commonJS({
title, title,
body, body,
audience, audience,
sentByAdminUserId: actorId, sentByAdminId: actorId,
recipients: { create: customers.map((c) => ({ customerId: c.id })) } recipients: { create: customers.map((c) => ({ customerId: c.id })) }
}, },
include: { _count: { select: { recipients: true } } } include: { _count: { select: { recipients: true } } }
@@ -102589,7 +102699,7 @@ var require_admin_misc_service = __commonJS({
title, title,
body, body,
audience: client_1.MessageAudience.SINGLE_NUMBER, audience: client_1.MessageAudience.SINGLE_NUMBER,
sentByAdminUserId: actorId, sentByAdminId: actorId,
recipients: { create: [{ customerId }] } recipients: { create: [{ customerId }] }
} }
}); });
@@ -102599,7 +102709,7 @@ var require_admin_misc_service = __commonJS({
const n = await this.prisma.notification.findUnique({ const n = await this.prisma.notification.findUnique({
where: { id }, where: { id },
include: { include: {
sentByAdminUser: true, sentByAdmin: true,
recipients: { include: { customer: true } } recipients: { include: { customer: true } }
} }
}); });
@@ -102611,7 +102721,7 @@ var require_admin_misc_service = __commonJS({
body: n.body, body: n.body,
text: n.body, text: n.body,
audience: n.audience, audience: n.audience,
sender: n.sentByAdminUser?.fullName ?? null, sender: n.sentByAdmin?.fullName ?? null,
caj: n.createdAt, caj: n.createdAt,
recipients: n.recipients.map((r) => ({ recipients: n.recipients.map((r) => ({
id: r.id, id: r.id,
@@ -102638,7 +102748,7 @@ var require_admin_misc_service = __commonJS({
skip, skip,
take, take,
include: { include: {
sentByAdminUser: true, sentByAdmin: true,
_count: { select: { recipients: true } } _count: { select: { recipients: true } }
} }
}), }),
@@ -102649,7 +102759,7 @@ var require_admin_misc_service = __commonJS({
title: n.title, title: n.title,
text: n.body, text: n.body,
audience: n.audience, audience: n.audience,
sender: n.sentByAdminUser?.fullName ?? null, sender: n.sentByAdmin?.fullName ?? null,
total_count: n._count.recipients, total_count: n._count.recipients,
caj: n.createdAt caj: n.createdAt
})), total, query, page, limit); })), total, query, page, limit);
@@ -0,0 +1,167 @@
-- Split panel staff out of "AdminUser" into their own table, "Admin".
--
-- WHY
-- ---
-- "AdminUser" held two unrelated kinds of person: panel staff and sales
-- experts (role SALES_EXPERT). Because the mobile is unique per table, one
-- number could not be both an admin and an expert. Staff belong to the ADMIN
-- PANEL only; experts belong to the apps. They are now separate tables, each
-- unique on its own.
--
-- SAFETY — READ BEFORE RUNNING ON PRODUCTION
-- ------------------------------------------
-- This migration NEVER modifies, deletes or locks "AdminUser" (the sales-expert
-- table; ~12,000 rows in production). It only
-- 1. creates "Admin" and its indexes/foreign keys,
-- 2. COPIES the non-expert rows from "AdminUser" into it, keeping their ids,
-- 3. re-points the "who did this" foreign keys from "AdminUser" to "Admin".
--
-- * Ids are preserved, so every issued panel token (its `sub` is the id) and
-- every existing ledger / status / SMS / upload row keeps resolving.
-- * The old non-expert rows are LEFT in "AdminUser", dormant. The code no
-- longer reads them as admins. Remove them later, in a separate release, only
-- once the split has been verified.
-- * Re-runnable: every statement is guarded (IF NOT EXISTS / ON CONFLICT / drop
-- then add), so a half-applied run can simply be run again.
-- * No data is moved on the big tables (LedgerEntry, OrderStatusEvent, …):
-- the existing columns keep their names and values; only the constraint's
-- target changes.
--
-- The re-pointed foreign keys are added NOT VALID (checked for new/changed rows
-- immediately, existing rows not scanned under a heavy lock) and then validated
-- at the end. A row that references an id that is not an admin — only possible
-- if a sales expert once appears as an actor — leaves that one constraint
-- NOT VALID and prints a NOTICE instead of failing the deploy. See
-- docs/ADMIN-TABLE-SPLIT.md for the check query.
--
-- ROLLBACK: deploy the previous code. Nothing was deleted, so the old code
-- still finds every admin in "AdminUser". Admins created AFTER the split exist
-- only in "Admin" and would need re-creating in the old system.
-- ── 1. The table ───────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS "Admin" (
"id" TEXT NOT NULL,
"fullName" TEXT NOT NULL,
"firstName" TEXT,
"lastName" TEXT,
"mobile" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"role" "AdminRole" NOT NULL,
"userTypeId" INTEGER,
"branchId" TEXT,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Admin_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX IF NOT EXISTS "Admin_mobile_key" ON "Admin" ("mobile");
CREATE INDEX IF NOT EXISTS "Admin_role_idx" ON "Admin" ("role");
CREATE INDEX IF NOT EXISTS "Admin_userTypeId_idx" ON "Admin" ("userTypeId");
CREATE INDEX IF NOT EXISTS "Admin_firstName_idx" ON "Admin" ("firstName");
CREATE INDEX IF NOT EXISTS "Admin_lastName_idx" ON "Admin" ("lastName");
DO $$ BEGIN
ALTER TABLE "Admin" ADD CONSTRAINT "Admin_userTypeId_fkey"
FOREIGN KEY ("userTypeId") REFERENCES "user_type"("id") ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
ALTER TABLE "Admin" ADD CONSTRAINT "Admin_branchId_fkey"
FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE;
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
-- ── 2. Copy the staff (everyone who is not a sales expert), ids preserved ──
--
-- Done BEFORE the name-sync trigger exists, so the stored names are copied
-- exactly as they are rather than rebuilt.
INSERT INTO "Admin"
("id", "fullName", "firstName", "lastName", "mobile", "passwordHash",
"role", "userTypeId", "branchId", "isActive", "createdAt")
SELECT
"id", "fullName", "firstName", "lastName", "mobile", "passwordHash",
"role", "userTypeId", "branchId", "isActive", "createdAt"
FROM "AdminUser"
WHERE "role" <> 'SALES_EXPERT'
ON CONFLICT ("id") DO NOTHING;
-- Same two-way fullName <-> firstName/lastName sync as AdminUser, sharing its
-- function (created in 20260922180000_sales_expert_profile_and_admin_name_parts).
DROP TRIGGER IF EXISTS admin_sync_full_name_trg ON "Admin";
CREATE TRIGGER admin_sync_full_name_trg
BEFORE INSERT OR UPDATE ON "Admin"
FOR EACH ROW EXECUTE FUNCTION admin_user_sync_full_name();
-- ── 3. Re-point the foreign keys that mean "an admin did this" ─────────────
--
-- Same constraint names, same column names, same ON DELETE / ON UPDATE actions;
-- only the referenced table changes. "Order"."salesAgentId",
-- "Customer"."referredByAgentId" and "SalesExpertProfile"."adminUserId" are
-- about SALES EXPERTS and keep pointing at "AdminUser".
ALTER TABLE "Workshop" DROP CONSTRAINT IF EXISTS "Workshop_supervisorId_fkey";
ALTER TABLE "Workshop" ADD CONSTRAINT "Workshop_supervisorId_fkey"
FOREIGN KEY ("supervisorId") REFERENCES "Admin"("id") ON DELETE SET NULL ON UPDATE CASCADE NOT VALID;
ALTER TABLE "OrderStatusEvent" DROP CONSTRAINT IF EXISTS "OrderStatusEvent_actorAdminUserId_fkey";
ALTER TABLE "OrderStatusEvent" ADD CONSTRAINT "OrderStatusEvent_actorAdminUserId_fkey"
FOREIGN KEY ("actorAdminUserId") REFERENCES "Admin"("id") ON DELETE SET NULL ON UPDATE CASCADE NOT VALID;
ALTER TABLE "PreOrderStatusEvent" DROP CONSTRAINT IF EXISTS "PreOrderStatusEvent_actorAdminUserId_fkey";
ALTER TABLE "PreOrderStatusEvent" ADD CONSTRAINT "PreOrderStatusEvent_actorAdminUserId_fkey"
FOREIGN KEY ("actorAdminUserId") REFERENCES "Admin"("id") ON DELETE SET NULL ON UPDATE CASCADE NOT VALID;
ALTER TABLE "LedgerEntry" DROP CONSTRAINT IF EXISTS "LedgerEntry_postedByAdminUserId_fkey";
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_postedByAdminUserId_fkey"
FOREIGN KEY ("postedByAdminUserId") REFERENCES "Admin"("id") ON DELETE SET NULL ON UPDATE CASCADE NOT VALID;
ALTER TABLE "BranchAllocation" DROP CONSTRAINT IF EXISTS "BranchAllocation_allocatorAdminUserId_fkey";
ALTER TABLE "BranchAllocation" ADD CONSTRAINT "BranchAllocation_allocatorAdminUserId_fkey"
FOREIGN KEY ("allocatorAdminUserId") REFERENCES "Admin"("id") ON DELETE SET NULL ON UPDATE CASCADE NOT VALID;
ALTER TABLE "SmsMessage" DROP CONSTRAINT IF EXISTS "SmsMessage_sentByAdminUserId_fkey";
ALTER TABLE "SmsMessage" ADD CONSTRAINT "SmsMessage_sentByAdminUserId_fkey"
FOREIGN KEY ("sentByAdminUserId") REFERENCES "Admin"("id") ON DELETE SET NULL ON UPDATE CASCADE NOT VALID;
ALTER TABLE "Notification" DROP CONSTRAINT IF EXISTS "Notification_sentByAdminUserId_fkey";
ALTER TABLE "Notification" ADD CONSTRAINT "Notification_sentByAdminUserId_fkey"
FOREIGN KEY ("sentByAdminUserId") REFERENCES "Admin"("id") ON DELETE SET NULL ON UPDATE CASCADE NOT VALID;
ALTER TABLE "UploadedFile" DROP CONSTRAINT IF EXISTS "UploadedFile_uploadedByAdminUserId_fkey";
ALTER TABLE "UploadedFile" ADD CONSTRAINT "UploadedFile_uploadedByAdminUserId_fkey"
FOREIGN KEY ("uploadedByAdminUserId") REFERENCES "Admin"("id") ON DELETE SET NULL ON UPDATE CASCADE NOT VALID;
-- Panel tokens are issued to staff only. CASCADE as before: deleting an admin
-- ends their sessions.
ALTER TABLE "AdminSession" DROP CONSTRAINT IF EXISTS "AdminSession_adminUserId_fkey";
ALTER TABLE "AdminSession" ADD CONSTRAINT "AdminSession_adminUserId_fkey"
FOREIGN KEY ("adminUserId") REFERENCES "Admin"("id") ON DELETE CASCADE ON UPDATE CASCADE NOT VALID;
-- ── 4. Validate; never fail the deploy over an old orphan ──────────────────
DO $$
DECLARE
c record;
BEGIN
FOR c IN
SELECT * FROM (VALUES
('Workshop', 'Workshop_supervisorId_fkey'),
('OrderStatusEvent', 'OrderStatusEvent_actorAdminUserId_fkey'),
('PreOrderStatusEvent', 'PreOrderStatusEvent_actorAdminUserId_fkey'),
('LedgerEntry', 'LedgerEntry_postedByAdminUserId_fkey'),
('BranchAllocation', 'BranchAllocation_allocatorAdminUserId_fkey'),
('SmsMessage', 'SmsMessage_sentByAdminUserId_fkey'),
('Notification', 'Notification_sentByAdminUserId_fkey'),
('UploadedFile', 'UploadedFile_uploadedByAdminUserId_fkey'),
('AdminSession', 'AdminSession_adminUserId_fkey')
) AS t(tbl, con)
LOOP
BEGIN
EXECUTE format('ALTER TABLE %I VALIDATE CONSTRAINT %I', c.tbl, c.con);
EXCEPTION WHEN foreign_key_violation THEN
RAISE NOTICE '% on % left NOT VALID: some existing rows reference an id that is not in "Admin" (see docs/ADMIN-TABLE-SPLIT.md)', c.con, c.tbl;
END;
END LOOP;
END $$;
+4 -4
View File
@@ -1846,7 +1846,7 @@ async function main() {
update: {}, update: {},
create: { id: "workshop-central", name: "\u06A9\u0627\u0631\u06AF\u0627\u0647 \u0645\u0631\u06A9\u0632\u06CC \u0633\u0627\u062E\u062A \u0637\u0644\u0627" } create: { id: "workshop-central", name: "\u06A9\u0627\u0631\u06AF\u0627\u0647 \u0645\u0631\u06A9\u0632\u06CC \u0633\u0627\u062E\u062A \u0637\u0644\u0627" }
}); });
await prisma.adminUser.upsert({ await prisma.admin.upsert({
where: { mobile: DEV_TEST_PHONE }, where: { mobile: DEV_TEST_PHONE },
update: { passwordHash: DEV_TEST_PHONE_PASSWORD_HASH, userTypeId: 85, branchId: null }, update: { passwordHash: DEV_TEST_PHONE_PASSWORD_HASH, userTypeId: 85, branchId: null },
create: { create: {
@@ -1858,7 +1858,7 @@ async function main() {
branchId: null branchId: null
} }
}); });
await prisma.adminUser.upsert({ await prisma.admin.upsert({
where: { mobile: "09120000001" }, where: { mobile: "09120000001" },
update: { passwordHash: PLACEHOLDER_PASSWORD_HASH, userTypeId: 85, branchId: null }, update: { passwordHash: PLACEHOLDER_PASSWORD_HASH, userTypeId: 85, branchId: null },
create: { create: {
@@ -1899,7 +1899,7 @@ async function main() {
wagePercent: 1.2 wagePercent: 1.2
} }
}); });
await prisma.adminUser.upsert({ await prisma.admin.upsert({
where: { mobile: "09123334444" }, where: { mobile: "09123334444" },
update: { passwordHash: PLACEHOLDER_PASSWORD_HASH, userTypeId: 88 }, update: { passwordHash: PLACEHOLDER_PASSWORD_HASH, userTypeId: 88 },
create: { create: {
@@ -1910,7 +1910,7 @@ async function main() {
userTypeId: 88 userTypeId: 88
} }
}); });
await prisma.adminUser.upsert({ await prisma.admin.upsert({
where: { mobile: "09125556666" }, where: { mobile: "09125556666" },
update: { passwordHash: PLACEHOLDER_PASSWORD_HASH, userTypeId: 87, branchId: null }, update: { passwordHash: PLACEHOLDER_PASSWORD_HASH, userTypeId: 87, branchId: null },
create: { create: {
File diff suppressed because one or more lines are too long
+23 -8
View File
@@ -130,6 +130,20 @@ exports.Prisma.CityScalarFieldEnum = {
percentAux: 'percentAux' percentAux: 'percentAux'
}; };
exports.Prisma.AdminScalarFieldEnum = {
id: 'id',
fullName: 'fullName',
firstName: 'firstName',
lastName: 'lastName',
mobile: 'mobile',
passwordHash: 'passwordHash',
role: 'role',
userTypeId: 'userTypeId',
branchId: 'branchId',
isActive: 'isActive',
createdAt: 'createdAt'
};
exports.Prisma.AdminUserScalarFieldEnum = { exports.Prisma.AdminUserScalarFieldEnum = {
id: 'id', id: 'id',
fullName: 'fullName', fullName: 'fullName',
@@ -170,7 +184,7 @@ exports.Prisma.AppSessionScalarFieldEnum = {
exports.Prisma.AdminSessionScalarFieldEnum = { exports.Prisma.AdminSessionScalarFieldEnum = {
id: 'id', id: 'id',
jti: 'jti', jti: 'jti',
adminUserId: 'adminUserId', adminId: 'adminId',
iamSessionId: 'iamSessionId', iamSessionId: 'iamSessionId',
iamSub: 'iamSub', iamSub: 'iamSub',
ipAddress: 'ipAddress', ipAddress: 'ipAddress',
@@ -370,7 +384,7 @@ exports.Prisma.OrderStatusEventScalarFieldEnum = {
fromStatus: 'fromStatus', fromStatus: 'fromStatus',
toStatus: 'toStatus', toStatus: 'toStatus',
note: 'note', note: 'note',
actorAdminUserId: 'actorAdminUserId', actorAdminId: 'actorAdminId',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -397,7 +411,7 @@ exports.Prisma.PreOrderStatusEventScalarFieldEnum = {
id: 'id', id: 'id',
preOrderId: 'preOrderId', preOrderId: 'preOrderId',
changes: 'changes', changes: 'changes',
actorAdminUserId: 'actorAdminUserId', actorAdminId: 'actorAdminId',
ipAddress: 'ipAddress', ipAddress: 'ipAddress',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -414,7 +428,7 @@ exports.Prisma.LedgerEntryScalarFieldEnum = {
orderId: 'orderId', orderId: 'orderId',
confirmed: 'confirmed', confirmed: 'confirmed',
confirmedAt: 'confirmedAt', confirmedAt: 'confirmedAt',
postedByAdminUserId: 'postedByAdminUserId', postedByAdminId: 'postedByAdminId',
labName: 'labName', labName: 'labName',
hallmark: 'hallmark', hallmark: 'hallmark',
accountName: 'accountName', accountName: 'accountName',
@@ -549,7 +563,7 @@ exports.Prisma.PromotionExclusionScalarFieldEnum = {
exports.Prisma.BranchAllocationScalarFieldEnum = { exports.Prisma.BranchAllocationScalarFieldEnum = {
id: 'id', id: 'id',
branchId: 'branchId', branchId: 'branchId',
allocatorAdminUserId: 'allocatorAdminUserId', allocatorAdminId: 'allocatorAdminId',
note: 'note', note: 'note',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -567,7 +581,7 @@ exports.Prisma.SmsMessageScalarFieldEnum = {
recipients: 'recipients', recipients: 'recipients',
body: 'body', body: 'body',
sentCount: 'sentCount', sentCount: 'sentCount',
sentByAdminUserId: 'sentByAdminUserId', sentByAdminId: 'sentByAdminId',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -576,7 +590,7 @@ exports.Prisma.NotificationScalarFieldEnum = {
title: 'title', title: 'title',
body: 'body', body: 'body',
audience: 'audience', audience: 'audience',
sentByAdminUserId: 'sentByAdminUserId', sentByAdminId: 'sentByAdminId',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -609,7 +623,7 @@ exports.Prisma.UploadedFileScalarFieldEnum = {
mimeType: 'mimeType', mimeType: 'mimeType',
sizeBytes: 'sizeBytes', sizeBytes: 'sizeBytes',
purpose: 'purpose', purpose: 'purpose',
uploadedByAdminUserId: 'uploadedByAdminUserId', uploadedByAdminId: 'uploadedByAdminId',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -782,6 +796,7 @@ exports.MessageAudience = exports.$Enums.MessageAudience = {
exports.Prisma.ModelName = { exports.Prisma.ModelName = {
City: 'City', City: 'City',
Admin: 'Admin',
AdminUser: 'AdminUser', AdminUser: 'AdminUser',
AppSession: 'AppSession', AppSession: 'AppSession',
AdminSession: 'AdminSession', AdminSession: 'AdminSession',
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "prisma-client-8945b25ad1891f9eaff8d38ca7f3c1b19a3f2d11c0c8521549c219186ff7566b", "name": "prisma-client-d98bddf3599c950eb3fcd511efa7f226c4ef4c5c3cbc261683fb1118a763a924",
"main": "index.js", "main": "index.js",
"types": "index.d.ts", "types": "index.d.ts",
"browser": "index-browser.js", "browser": "index-browser.js",
+92 -48
View File
@@ -140,6 +140,61 @@ model City {
@@index([parentId]) @@index([parentId])
} }
/// A staff account for the ADMIN PANEL — and nothing else.
///
/// Split out of AdminUser (which now holds sales experts only) so that:
/// * the same mobile can be an admin AND a sales expert — they are two
/// separate roles, each unique only within its own table;
/// * the expert table (production: ~12,000 rows) is never written by panel
/// staff management.
/// Migration 20261026120000_split_admin_table copies the existing non-expert
/// AdminUser rows here WITH THEIR IDS, so issued panel tokens and every
/// "who did this" column keep resolving. The old rows are left in AdminUser,
/// dormant; no code reads them as admins any more.
///
/// ⚠️ fullName/firstName/lastName are kept in sync by the DB trigger
/// `admin_sync_full_name_trg` (it shares the function admin_user_sync_full_name) — same semantics as AdminUser: the parts win.
model Admin {
id String @id @default(cuid())
fullName String
firstName String?
lastName String?
mobile String @unique
passwordHash String
/// Never SALES_EXPERT — that role lives in AdminUser. Enforced in
/// AdminUsersService, not in the database, so the enum stays shared.
role AdminRole
userTypeId Int?
userType UserType? @relation(fields: [userTypeId], references: [id])
branchId String?
branch Branch? @relation(fields: [branchId], references: [id])
isActive Boolean @default(true)
createdAt DateTime @default(now())
supervisedWorkshops Workshop[] @relation("WorkshopSupervisor")
branchAllocations BranchAllocation[] @relation("AllocationAuthor")
postedLedgerEntries LedgerEntry[]
orderStatusEvents OrderStatusEvent[]
preOrderStatusEvents PreOrderStatusEvent[]
sentSmsMessages SmsMessage[] @relation("SmsAuthor")
sentNotifications Notification[] @relation("NotificationAuthor")
uploadedFiles UploadedFile[] @relation("FileUploader")
/// Issued admin-panel tokens, recorded only while session tracking is on
/// (AUTH_SESSION_TRACKING, or implicitly whenever the IAM is enabled) so a
/// token can be revoked before it expires. See AdminSession.
sessions AdminSession[]
@@index([role])
@@index([userTypeId])
@@index([firstName])
@@index([lastName])
}
/// A SALES EXPERT (role SALES_EXPERT). Panel staff live in `Admin`; rows here with
/// any other role are pre-split leftovers and are ignored by the code.
model AdminUser { model AdminUser {
id String @id @default(cuid()) id String @id @default(cuid())
@@ -185,26 +240,13 @@ model AdminUser {
pasazhName String? // legacy pasazh_name pasazhName String? // legacy pasazh_name
memberCode String? // legacy mcode memberCode String? // legacy mcode
referredCustomers Customer[] @relation("ReferralAgent") referredCustomers Customer[] @relation("ReferralAgent")
/// Orders credited to this sales expert — see Order.salesAgentId. /// Orders credited to this sales expert — see Order.salesAgentId.
creditedOrders Order[] @relation("OrderSalesAgent") creditedOrders Order[] @relation("OrderSalesAgent")
supervisedWorkshops Workshop[] @relation("WorkshopSupervisor")
branchAllocations BranchAllocation[] @relation("AllocationAuthor")
postedLedgerEntries LedgerEntry[]
orderStatusEvents OrderStatusEvent[]
preOrderStatusEvents PreOrderStatusEvent[]
sentSmsMessages SmsMessage[] @relation("SmsAuthor")
sentNotifications Notification[] @relation("NotificationAuthor")
uploadedFiles UploadedFile[] @relation("FileUploader")
/// Only ever set for role = SALES_EXPERT (D17). /// Only ever set for role = SALES_EXPERT (D17).
salesExpertProfile SalesExpertProfile? salesExpertProfile SalesExpertProfile?
/// Issued admin-panel tokens, recorded only while session tracking is on
/// (AUTH_SESSION_TRACKING, or implicitly whenever the IAM is enabled) so a
/// token can be revoked before it expires. See AdminSession.
sessions AdminSession[]
@@index([role]) @@index([role])
@@index([userTypeId]) @@index([userTypeId])
@@index([avatarFileId]) @@index([avatarFileId])
@@ -274,8 +316,8 @@ model AdminSession {
/// The token's `jti` claim. Unique: one row per issued token. /// The token's `jti` claim. Unique: one row per issued token.
jti String @unique jti String @unique
adminUserId String adminId String @map("adminUserId")
adminUser AdminUser @relation(fields: [adminUserId], references: [id], onDelete: Cascade) admin Admin @relation(fields: [adminId], references: [id], onDelete: Cascade)
/// Set when the login was verified by the IAM: its session id (`sid`) and the /// Set when the login was verified by the IAM: its session id (`sid`) and the
/// stable user id (`sub`) there. Back-channel logout arrives keyed by these — /// stable user id (`sub`) there. Back-channel logout arrives keyed by these —
@@ -294,7 +336,7 @@ model AdminSession {
revokedAt DateTime? revokedAt DateTime?
revokedReason String? revokedReason String?
@@index([adminUserId]) @@index([adminId], map: "AdminSession_adminUserId_idx")
@@index([iamSessionId]) @@index([iamSessionId])
@@index([iamSub]) @@index([iamSub])
@@index([expiresAt]) @@index([expiresAt])
@@ -571,6 +613,7 @@ model Branch {
cityId String? cityId String?
city City? @relation(fields: [cityId], references: [id]) city City? @relation(fields: [cityId], references: [id])
adminUsers AdminUser[] adminUsers AdminUser[]
admins Admin[]
orders Order[] orders Order[]
allocations BranchAllocation[] allocations BranchAllocation[]
} }
@@ -583,7 +626,7 @@ model Workshop {
phone String? phone String?
/// The supervisor created alongside the workshop by the legacy Add form. /// The supervisor created alongside the workshop by the legacy Add form.
supervisorId String? supervisorId String?
supervisor AdminUser? @relation("WorkshopSupervisor", fields: [supervisorId], references: [id]) supervisor Admin? @relation("WorkshopSupervisor", fields: [supervisorId], references: [id])
isActive Boolean @default(true) isActive Boolean @default(true)
preOrders PreOrder[] preOrders PreOrder[]
} }
@@ -826,15 +869,15 @@ model OrderItem {
} }
model OrderStatusEvent { model OrderStatusEvent {
id String @id @default(cuid()) id String @id @default(cuid())
orderId String orderId String
order Order @relation(fields: [orderId], references: [id]) order Order @relation(fields: [orderId], references: [id])
fromStatus OrderStatus? fromStatus OrderStatus?
toStatus OrderStatus toStatus OrderStatus
note String? note String?
actorAdminUserId String? actorAdminId String? @map("actorAdminUserId")
actorAdminUser AdminUser? @relation(fields: [actorAdminUserId], references: [id]) actorAdmin Admin? @relation(fields: [actorAdminId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@index([orderId]) @@index([orderId])
} }
@@ -875,15 +918,15 @@ model PreOrder {
} }
model PreOrderStatusEvent { model PreOrderStatusEvent {
id String @id @default(cuid()) id String @id @default(cuid())
preOrderId String preOrderId String
preOrder PreOrder @relation(fields: [preOrderId], references: [id]) preOrder PreOrder @relation(fields: [preOrderId], references: [id])
/// Field-level diff, e.g. {"status": {"old": "PLACED", "new": "ADMIN_APPROVED"}} /// Field-level diff, e.g. {"status": {"old": "PLACED", "new": "ADMIN_APPROVED"}}
changes Json changes Json
actorAdminUserId String? actorAdminId String? @map("actorAdminUserId")
actorAdminUser AdminUser? @relation(fields: [actorAdminUserId], references: [id]) actorAdmin Admin? @relation(fields: [actorAdminId], references: [id])
ipAddress String? ipAddress String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@index([preOrderId]) @@index([preOrderId])
} }
@@ -912,10 +955,10 @@ model LedgerEntry {
order Order? @relation(fields: [orderId], references: [id]) order Order? @relation(fields: [orderId], references: [id])
/// Pending entries don't count toward the cached customer balance until confirmed. /// Pending entries don't count toward the cached customer balance until confirmed.
confirmed Boolean @default(false) confirmed Boolean @default(false)
confirmedAt DateTime? confirmedAt DateTime?
postedByAdminUserId String? postedByAdminId String? @map("postedByAdminUserId")
postedByAdminUser AdminUser? @relation(fields: [postedByAdminUserId], references: [id]) postedByAdmin Admin? @relation(fields: [postedByAdminId], references: [id])
/// Instrument metadata from the legacy "cash" document form. Which fields are /// Instrument metadata from the legacy "cash" document form. Which fields are
/// filled depends on sourceType: a melted-gold receipt carries lab/hallmark and /// filled depends on sourceType: a melted-gold receipt carries lab/hallmark and
@@ -997,6 +1040,7 @@ model UserType {
permissions Permission[] permissions Permission[]
adminUsers AdminUser[] adminUsers AdminUser[]
admins Admin[]
@@map("user_type") @@map("user_type")
} }
@@ -1251,8 +1295,8 @@ model BranchAllocation {
branch Branch @relation(fields: [branchId], references: [id]) branch Branch @relation(fields: [branchId], references: [id])
/// Legacy `allocator_user` — the admin who filed the allocation. /// Legacy `allocator_user` — the admin who filed the allocation.
allocatorAdminUserId String? allocatorAdminId String? @map("allocatorAdminUserId")
allocatorAdminUser AdminUser? @relation("AllocationAuthor", fields: [allocatorAdminUserId], references: [id]) allocatorAdmin Admin? @relation("AllocationAuthor", fields: [allocatorAdminId], references: [id])
note String? note String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -1299,8 +1343,8 @@ model SmsMessage {
/// How many numbers the send actually resolved to. /// How many numbers the send actually resolved to.
sentCount Int @default(0) sentCount Int @default(0)
sentByAdminUserId String? sentByAdminId String? @map("sentByAdminUserId")
sentByAdminUser AdminUser? @relation("SmsAuthor", fields: [sentByAdminUserId], references: [id]) sentByAdmin Admin? @relation("SmsAuthor", fields: [sentByAdminId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -1313,8 +1357,8 @@ model Notification {
body String body String
audience MessageAudience @default(ALL_CUSTOMERS) audience MessageAudience @default(ALL_CUSTOMERS)
sentByAdminUserId String? sentByAdminId String? @map("sentByAdminUserId")
sentByAdminUser AdminUser? @relation("NotificationAuthor", fields: [sentByAdminUserId], references: [id]) sentByAdmin Admin? @relation("NotificationAuthor", fields: [sentByAdminId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
@@ -1379,8 +1423,8 @@ model UploadedFile {
/// 'customer.nationalId'. Kept loose so a new form needs no schema change. /// 'customer.nationalId'. Kept loose so a new form needs no schema change.
purpose String? purpose String?
uploadedByAdminUserId String? uploadedByAdminId String? @map("uploadedByAdminUserId")
uploadedByAdminUser AdminUser? @relation("FileUploader", fields: [uploadedByAdminUserId], references: [id]) uploadedByAdmin Admin? @relation("FileUploader", fields: [uploadedByAdminId], references: [id])
createdAt DateTime @default(now()) createdAt DateTime @default(now())
customerAvatars Customer[] @relation("CustomerAvatar") customerAvatars Customer[] @relation("CustomerAvatar")
+23 -8
View File
@@ -130,6 +130,20 @@ exports.Prisma.CityScalarFieldEnum = {
percentAux: 'percentAux' percentAux: 'percentAux'
}; };
exports.Prisma.AdminScalarFieldEnum = {
id: 'id',
fullName: 'fullName',
firstName: 'firstName',
lastName: 'lastName',
mobile: 'mobile',
passwordHash: 'passwordHash',
role: 'role',
userTypeId: 'userTypeId',
branchId: 'branchId',
isActive: 'isActive',
createdAt: 'createdAt'
};
exports.Prisma.AdminUserScalarFieldEnum = { exports.Prisma.AdminUserScalarFieldEnum = {
id: 'id', id: 'id',
fullName: 'fullName', fullName: 'fullName',
@@ -170,7 +184,7 @@ exports.Prisma.AppSessionScalarFieldEnum = {
exports.Prisma.AdminSessionScalarFieldEnum = { exports.Prisma.AdminSessionScalarFieldEnum = {
id: 'id', id: 'id',
jti: 'jti', jti: 'jti',
adminUserId: 'adminUserId', adminId: 'adminId',
iamSessionId: 'iamSessionId', iamSessionId: 'iamSessionId',
iamSub: 'iamSub', iamSub: 'iamSub',
ipAddress: 'ipAddress', ipAddress: 'ipAddress',
@@ -370,7 +384,7 @@ exports.Prisma.OrderStatusEventScalarFieldEnum = {
fromStatus: 'fromStatus', fromStatus: 'fromStatus',
toStatus: 'toStatus', toStatus: 'toStatus',
note: 'note', note: 'note',
actorAdminUserId: 'actorAdminUserId', actorAdminId: 'actorAdminId',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -397,7 +411,7 @@ exports.Prisma.PreOrderStatusEventScalarFieldEnum = {
id: 'id', id: 'id',
preOrderId: 'preOrderId', preOrderId: 'preOrderId',
changes: 'changes', changes: 'changes',
actorAdminUserId: 'actorAdminUserId', actorAdminId: 'actorAdminId',
ipAddress: 'ipAddress', ipAddress: 'ipAddress',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -414,7 +428,7 @@ exports.Prisma.LedgerEntryScalarFieldEnum = {
orderId: 'orderId', orderId: 'orderId',
confirmed: 'confirmed', confirmed: 'confirmed',
confirmedAt: 'confirmedAt', confirmedAt: 'confirmedAt',
postedByAdminUserId: 'postedByAdminUserId', postedByAdminId: 'postedByAdminId',
labName: 'labName', labName: 'labName',
hallmark: 'hallmark', hallmark: 'hallmark',
accountName: 'accountName', accountName: 'accountName',
@@ -549,7 +563,7 @@ exports.Prisma.PromotionExclusionScalarFieldEnum = {
exports.Prisma.BranchAllocationScalarFieldEnum = { exports.Prisma.BranchAllocationScalarFieldEnum = {
id: 'id', id: 'id',
branchId: 'branchId', branchId: 'branchId',
allocatorAdminUserId: 'allocatorAdminUserId', allocatorAdminId: 'allocatorAdminId',
note: 'note', note: 'note',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -567,7 +581,7 @@ exports.Prisma.SmsMessageScalarFieldEnum = {
recipients: 'recipients', recipients: 'recipients',
body: 'body', body: 'body',
sentCount: 'sentCount', sentCount: 'sentCount',
sentByAdminUserId: 'sentByAdminUserId', sentByAdminId: 'sentByAdminId',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -576,7 +590,7 @@ exports.Prisma.NotificationScalarFieldEnum = {
title: 'title', title: 'title',
body: 'body', body: 'body',
audience: 'audience', audience: 'audience',
sentByAdminUserId: 'sentByAdminUserId', sentByAdminId: 'sentByAdminId',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -609,7 +623,7 @@ exports.Prisma.UploadedFileScalarFieldEnum = {
mimeType: 'mimeType', mimeType: 'mimeType',
sizeBytes: 'sizeBytes', sizeBytes: 'sizeBytes',
purpose: 'purpose', purpose: 'purpose',
uploadedByAdminUserId: 'uploadedByAdminUserId', uploadedByAdminId: 'uploadedByAdminId',
createdAt: 'createdAt' createdAt: 'createdAt'
}; };
@@ -782,6 +796,7 @@ exports.MessageAudience = exports.$Enums.MessageAudience = {
exports.Prisma.ModelName = { exports.Prisma.ModelName = {
City: 'City', City: 'City',
Admin: 'Admin',
AdminUser: 'AdminUser', AdminUser: 'AdminUser',
AppSession: 'AppSession', AppSession: 'AppSession',
AdminSession: 'AdminSession', AdminSession: 'AdminSession',