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:
co-authored by
Claude Sonnet 5.5
parent
06c8a5f434
commit
e2ec30f241
+211
-101
@@ -90343,7 +90343,7 @@ var require_admin_uploads_service = __commonJS({
|
||||
mimeType: file2.mimetype,
|
||||
sizeBytes: file2.size,
|
||||
purpose: purpose ?? null,
|
||||
uploadedByAdminUserId: actorAdminUserId
|
||||
uploadedByAdminId: actorAdminUserId
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -91521,7 +91521,7 @@ var require_ledger_service = __commonJS({
|
||||
note: input.note,
|
||||
confirmed,
|
||||
confirmedAt: confirmed ? /* @__PURE__ */ new Date() : null,
|
||||
postedByAdminUserId: input.postedByAdminUserId
|
||||
postedByAdminId: input.postedByAdminId
|
||||
}
|
||||
});
|
||||
if (confirmed) {
|
||||
@@ -91546,7 +91546,7 @@ var require_ledger_service = __commonJS({
|
||||
return entry;
|
||||
const updated = await tx.ledgerEntry.update({
|
||||
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);
|
||||
return updated;
|
||||
@@ -91660,7 +91660,7 @@ var require_ledger_service = __commonJS({
|
||||
note: params.note,
|
||||
confirmed: false,
|
||||
// pending until an admin confirms — intentional deviation from legacy, which auto-confirms on creation
|
||||
postedByAdminUserId: params.postedByAdminUserId
|
||||
postedByAdminId: params.postedByAdminId
|
||||
}, tx);
|
||||
}
|
||||
/**
|
||||
@@ -91677,7 +91677,7 @@ var require_ledger_service = __commonJS({
|
||||
amount: params.amount,
|
||||
note: params.note,
|
||||
confirmed: false,
|
||||
postedByAdminUserId: params.postedByAdminUserId
|
||||
postedByAdminId: params.postedByAdminId
|
||||
}, tx);
|
||||
}
|
||||
async postAdjustment(params, tx = this.prisma) {
|
||||
@@ -91689,7 +91689,7 @@ var require_ledger_service = __commonJS({
|
||||
amount: params.amount,
|
||||
note: params.note,
|
||||
confirmed: true,
|
||||
postedByAdminUserId: params.postedByAdminUserId
|
||||
postedByAdminId: params.postedByAdminId
|
||||
}, tx);
|
||||
}
|
||||
async getBalance(customerId) {
|
||||
@@ -92254,7 +92254,7 @@ var require_orders_service = __commonJS({
|
||||
}
|
||||
});
|
||||
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);
|
||||
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({
|
||||
data: {
|
||||
jti: input.jti,
|
||||
adminUserId: input.adminUserId,
|
||||
adminId: input.adminId,
|
||||
iamSessionId: input.iamSessionId ?? null,
|
||||
iamSub: input.iamSub ?? null,
|
||||
ipAddress: input.ipAddress ?? null,
|
||||
@@ -93863,8 +93863,8 @@ var require_admin_session_service = __commonJS({
|
||||
return this.revokeWhere({ iamSub }, reason);
|
||||
}
|
||||
/** Revoke every session of a local admin (account disabled, password changed). */
|
||||
async revokeAllForAdmin(adminUserId, reason = "revoked") {
|
||||
return this.revokeWhere({ adminUserId }, reason);
|
||||
async revokeAllForAdmin(adminId, reason = "revoked") {
|
||||
return this.revokeWhere({ adminId }, reason);
|
||||
}
|
||||
async revokeWhere(where, reason) {
|
||||
if (!this.enabled)
|
||||
@@ -94003,7 +94003,7 @@ var require_auth_service = __commonJS({
|
||||
if (isStaticAdmin) {
|
||||
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 },
|
||||
include: { userType: { include: { permissions: true } } }
|
||||
});
|
||||
@@ -94023,7 +94023,7 @@ var require_auth_service = __commonJS({
|
||||
if (jti) {
|
||||
await this.sessions.record({
|
||||
jti,
|
||||
adminUserId: user.id,
|
||||
adminId: user.id,
|
||||
iamSessionId: verified.identity?.sessionId ?? null,
|
||||
iamSub: verified.identity?.sub ?? null,
|
||||
ipAddress: clientIp ?? null,
|
||||
@@ -94084,7 +94084,7 @@ var require_auth_service = __commonJS({
|
||||
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");
|
||||
}
|
||||
const user = await this.prisma.adminUser.findFirst({
|
||||
const user = await this.prisma.admin.findFirst({
|
||||
where: { mobile: { in: mobileVariants(claims.phoneNumber) }, isActive: true },
|
||||
include: { userType: { include: { permissions: true } } }
|
||||
});
|
||||
@@ -94128,7 +94128,7 @@ var require_auth_service = __commonJS({
|
||||
if (jti) {
|
||||
await this.sessions.record({
|
||||
jti,
|
||||
adminUserId: user.id,
|
||||
adminId: user.id,
|
||||
iamSessionId: identity?.sessionId ?? null,
|
||||
iamSub: identity?.sub ?? null,
|
||||
ipAddress: ip ?? null,
|
||||
@@ -94152,7 +94152,7 @@ var require_auth_service = __commonJS({
|
||||
const fullAccess = await this.prisma.userType.findUnique({ where: { id: 85 } });
|
||||
const userTypeId = fullAccess ? 85 : null;
|
||||
const passwordHash = await bcrypt.hash(STATIC_ADMIN_MOBILE, 10);
|
||||
await this.prisma.adminUser.upsert({
|
||||
await this.prisma.admin.upsert({
|
||||
where: { mobile: STATIC_ADMIN_MOBILE },
|
||||
update: { role: client_1.AdminRole.SUPER_ADMIN, userTypeId, branchId: null, isActive: true },
|
||||
create: {
|
||||
@@ -94230,7 +94230,7 @@ var require_auth_service = __commonJS({
|
||||
* legacy. The guard's own checks use the pre-expanded list from JwtStrategy.
|
||||
*/
|
||||
async showData(adminId) {
|
||||
const user = await this.prisma.adminUser.findUnique({
|
||||
const user = await this.prisma.admin.findUnique({
|
||||
where: { id: adminId },
|
||||
include: { userType: { include: { permissions: true } } }
|
||||
});
|
||||
@@ -94644,7 +94644,7 @@ var require_jwt_strategy = __commonJS({
|
||||
if (!await this.sessions.isActive(payload.jti)) {
|
||||
throw new common_12.UnauthorizedException();
|
||||
}
|
||||
const user = await this.prisma.adminUser.findUnique({
|
||||
const user = await this.prisma.admin.findUnique({
|
||||
where: { id: payload.sub },
|
||||
include: { userType: { include: { permissions: true } } }
|
||||
});
|
||||
@@ -95418,7 +95418,7 @@ var require_admin_orders_service = __commonJS({
|
||||
customer: true,
|
||||
branch: true,
|
||||
items: { include: { productVariant: { include: { product: true } } } },
|
||||
statusEvents: { include: { actorAdminUser: true }, orderBy: { createdAt: "asc" } }
|
||||
statusEvents: { include: { actorAdmin: true }, orderBy: { createdAt: "asc" } }
|
||||
}
|
||||
});
|
||||
if (!order)
|
||||
@@ -96336,7 +96336,7 @@ var require_admin_branches_service = __commonJS({
|
||||
/** Legacy `POST branch/table`. */
|
||||
async listBranches(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, {
|
||||
searchable: ["name", "address", "phone"],
|
||||
allowed: ["id", "name", "address", "phone", "isActive", "cityId", "city.name"],
|
||||
@@ -96351,8 +96351,8 @@ var require_admin_branches_service = __commonJS({
|
||||
take,
|
||||
include: {
|
||||
city: true,
|
||||
adminUsers: { where: { isActive: true }, take: 1 },
|
||||
_count: { select: { adminUsers: true, orders: true } }
|
||||
admins: { where: { isActive: true }, take: 1 },
|
||||
_count: { select: { admins: true, orders: true } }
|
||||
}
|
||||
}),
|
||||
this.prisma.branch.count({ where })
|
||||
@@ -96367,15 +96367,15 @@ var require_admin_branches_service = __commonJS({
|
||||
city_id: b.cityId,
|
||||
city: b.city ? { id: b.city.id, title: b.city.name } : null,
|
||||
// legacy column key is `user.name` — the branch manager
|
||||
user: b.adminUsers[0] ? { id: b.adminUsers[0].id, name: b.adminUsers[0].fullName } : null,
|
||||
users_count: b._count.adminUsers,
|
||||
user: b.admins[0] ? { id: b.admins[0].id, name: b.admins[0].fullName } : null,
|
||||
users_count: b._count.admins,
|
||||
orders_count: b._count.orders
|
||||
})), total, query, page, limit);
|
||||
}
|
||||
async findBranch(id) {
|
||||
const b = await this.prisma.branch.findUnique({
|
||||
where: { id },
|
||||
include: { city: true, adminUsers: { where: { isActive: true }, take: 1 } }
|
||||
include: { city: true, admins: { where: { isActive: true }, take: 1 } }
|
||||
});
|
||||
if (!b)
|
||||
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,
|
||||
is_active: b.isActive,
|
||||
city_id: b.cityId,
|
||||
user_id: b.adminUsers[0]?.id ?? null,
|
||||
user: b.adminUsers[0] ? { id: b.adminUsers[0].id, name: b.adminUsers[0].fullName } : null
|
||||
user_id: b.admins[0]?.id ?? 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. */
|
||||
@@ -96436,12 +96436,12 @@ var require_admin_branches_service = __commonJS({
|
||||
*/
|
||||
async assignBranchManager(branchId, userId) {
|
||||
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) {
|
||||
const user = await tx.adminUser.findUnique({ where: { id: userId } });
|
||||
const user = await tx.admin.findUnique({ where: { id: userId } });
|
||||
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");
|
||||
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");
|
||||
const fullName = `${fname} ${lname}`.trim();
|
||||
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 (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) {
|
||||
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 },
|
||||
data: { fullName, mobile: phone }
|
||||
});
|
||||
return currentId;
|
||||
}
|
||||
}
|
||||
const existing = await this.prisma.adminUser.findUnique({ where: { mobile: phone } });
|
||||
const existing = await this.prisma.admin.findUnique({ where: { mobile: phone } });
|
||||
if (existing)
|
||||
return existing.id;
|
||||
const created = await this.prisma.adminUser.create({
|
||||
const created = await this.prisma.admin.create({
|
||||
data: {
|
||||
fullName,
|
||||
mobile: phone,
|
||||
@@ -96605,7 +96605,7 @@ var require_admin_branches_service = __commonJS({
|
||||
}
|
||||
/** The admin picker the branch form uses — legacy `GET user/list`. */
|
||||
async userList() {
|
||||
const rows = await this.prisma.adminUser.findMany({
|
||||
const rows = await this.prisma.admin.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: { fullName: "asc" },
|
||||
select: { id: true, fullName: true, mobile: true, branchId: true }
|
||||
@@ -100173,7 +100173,7 @@ var require_admin_documents_service = __commonJS({
|
||||
orderBy,
|
||||
skip,
|
||||
take,
|
||||
include: { customer: true, postedByAdminUser: true }
|
||||
include: { customer: true, postedByAdmin: true }
|
||||
}),
|
||||
this.prisma.ledgerEntry.count({ where: scoped })
|
||||
]);
|
||||
@@ -100212,7 +100212,7 @@ var require_admin_documents_service = __commonJS({
|
||||
lname: e.customer.lastName ?? "",
|
||||
mobile: e.customer.mobile
|
||||
},
|
||||
posted_by: e.postedByAdminUser ? e.postedByAdminUser.fullName : null,
|
||||
posted_by: e.postedByAdmin ? e.postedByAdmin.fullName : null,
|
||||
caj: e.createdAt,
|
||||
uaj: e.createdAt
|
||||
};
|
||||
@@ -100221,7 +100221,7 @@ var require_admin_documents_service = __commonJS({
|
||||
async findOne(id) {
|
||||
const e = await this.prisma.ledgerEntry.findUnique({
|
||||
where: { id },
|
||||
include: { customer: true, postedByAdminUser: true }
|
||||
include: { customer: true, postedByAdmin: true }
|
||||
});
|
||||
if (!e)
|
||||
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,
|
||||
pricePerGramRial: data.price_per_gram ? String(data.price_per_gram) : void 0,
|
||||
note: data.note ?? void 0,
|
||||
postedByAdminUserId: actorAdminUserId
|
||||
postedByAdminId: actorAdminUserId
|
||||
});
|
||||
await this.prisma.ledgerEntry.update({
|
||||
where: { id: entry.id },
|
||||
@@ -101292,7 +101292,7 @@ var require_admin_users_service = __commonJS({
|
||||
caj: "createdAt",
|
||||
active: "isActive"
|
||||
};
|
||||
/** Legacy `GET users` — the admin user grid. */
|
||||
/** Legacy `GET users` — the admin user grid (panel staff only). */
|
||||
async list(query) {
|
||||
this.table.assertNotExport(query);
|
||||
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 } }]
|
||||
};
|
||||
const [rows, total] = await Promise.all([
|
||||
this.prisma.adminUser.findMany({
|
||||
this.prisma.admin.findMany({
|
||||
where: visible,
|
||||
orderBy,
|
||||
skip,
|
||||
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);
|
||||
}
|
||||
@@ -101345,20 +101345,20 @@ var require_admin_users_service = __commonJS({
|
||||
user_type: u.userType ? { id: u.userType.id, title: u.userType.title } : null,
|
||||
branch_id: u.branchId,
|
||||
branch: u.branch ? { id: u.branch.id, name: u.branch.name } : null,
|
||||
percent: u.wagePercent,
|
||||
percent: u.wagePercent ?? null,
|
||||
active: u.isActive ? 1 : -1,
|
||||
is_active: u.isActive,
|
||||
caj: u.createdAt,
|
||||
// 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.
|
||||
address: u.address,
|
||||
store_name: u.storeName,
|
||||
pasazh_name: u.pasazhName,
|
||||
mcode: u.memberCode,
|
||||
image_id: u.avatarFileId,
|
||||
address: u.address ?? null,
|
||||
store_name: u.storeName ?? null,
|
||||
pasazh_name: u.pasazhName ?? null,
|
||||
mcode: u.memberCode ?? null,
|
||||
image_id: u.avatarFileId ?? 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.
|
||||
city_id: u.salesExpertProfile?.cityId ?? 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
|
||||
};
|
||||
}
|
||||
async findOne(id) {
|
||||
const u = await this.prisma.adminUser.findUnique({
|
||||
/** A panel admin by id, or null. The support super admin is never found. */
|
||||
async findAdmin(id) {
|
||||
const admin = await this.prisma.admin.findUnique({
|
||||
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.
|
||||
include: {
|
||||
userType: true,
|
||||
@@ -101378,9 +101391,16 @@ var require_admin_users_service = __commonJS({
|
||||
salesExpertProfile: { include: { city: true } }
|
||||
}
|
||||
});
|
||||
if (!u || (0, super_admin_1.isSuperAdmin)(u.mobile))
|
||||
throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
|
||||
return this.toRow(u);
|
||||
return expert && !(0, super_admin_1.isSuperAdmin)(expert.mobile) ? expert : null;
|
||||
}
|
||||
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`.
|
||||
@@ -101389,6 +101409,8 @@ var require_admin_users_service = __commonJS({
|
||||
* caller omitted it, which silently turned every admin created without an
|
||||
* explicit role into a sales agent regardless of the access level ("نوع
|
||||
* کاربری") actually chosen. UserCreate.vue now always sends one.
|
||||
*
|
||||
* SALES_EXPERT goes to the `AdminUser` table, every other role to `Admin`.
|
||||
*/
|
||||
async create(data) {
|
||||
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");
|
||||
if (!role)
|
||||
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 } });
|
||||
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");
|
||||
@@ -101415,13 +101462,12 @@ var require_admin_users_service = __commonJS({
|
||||
lastName: lastName || null,
|
||||
mobile,
|
||||
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,
|
||||
branchId: data.branch_id || 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,
|
||||
// Sales-agent-only fields (ReagentCustomer.vue); undefined for every
|
||||
// other role, which Prisma leaves at column default (null).
|
||||
// Sales-agent-only fields (ReagentCustomer.vue).
|
||||
address: data.address || null,
|
||||
storeName: data.store_name || null,
|
||||
pasazhName: data.pasazh_name || null,
|
||||
@@ -101429,7 +101475,7 @@ var require_admin_users_service = __commonJS({
|
||||
avatarFileId: data.image_id || null,
|
||||
// D17 — a sales expert always gets its profile row, so the grid's city
|
||||
// and credit-limit columns have somewhere to read from immediately.
|
||||
...role === "SALES_EXPERT" ? { salesExpertProfile: { create: this.salesExpertProfileData(data) } } : {}
|
||||
salesExpertProfile: { create: this.salesExpertProfileData(data) }
|
||||
},
|
||||
include: {
|
||||
userType: true,
|
||||
@@ -101456,9 +101502,65 @@ var require_admin_users_service = __commonJS({
|
||||
}
|
||||
/** Legacy `PUT users/{id}`. */
|
||||
async update(id, data) {
|
||||
const existing = await this.prisma.adminUser.findUnique({ where: { id } });
|
||||
if (!existing || (0, super_admin_1.isSuperAdmin)(existing.mobile))
|
||||
throw new common_12.NotFoundException("\u06A9\u0627\u0631\u0628\u0631 \u06CC\u0627\u0641\u062A \u0646\u0634\u062F");
|
||||
const admin = await this.findAdmin(id);
|
||||
if (admin)
|
||||
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 = {};
|
||||
if (data.fname !== void 0 || data.lname !== void 0) {
|
||||
const firstName = String(data.fname ?? existing.firstName ?? "").trim();
|
||||
@@ -101479,8 +101581,9 @@ var require_admin_users_service = __commonJS({
|
||||
}
|
||||
if (data.password)
|
||||
patch.passwordHash = await bcrypt.hash(String(data.password), 10);
|
||||
if (data.role !== void 0)
|
||||
patch.role = data.role;
|
||||
if (data.role !== void 0 && data.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 \u0642\u0627\u0628\u0644 \u062A\u063A\u06CC\u06CC\u0631 \u0646\u06CC\u0633\u062A");
|
||||
}
|
||||
if (data.ut !== void 0) {
|
||||
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) {
|
||||
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;
|
||||
if (effectiveRole === "SALES_EXPERT" && (touchesProfile || patch.role !== void 0)) {
|
||||
if (touchesProfile) {
|
||||
const profile = this.salesExpertProfileData(data);
|
||||
patch.salesExpertProfile = {
|
||||
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 },
|
||||
data: patch,
|
||||
include: {
|
||||
@@ -101531,10 +101633,6 @@ var require_admin_users_service = __commonJS({
|
||||
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}`.
|
||||
@@ -101544,21 +101642,28 @@ var require_admin_users_service = __commonJS({
|
||||
* audit trail. Deactivation is offered instead. Legacy deleted regardless.
|
||||
*/
|
||||
async remove(id, actorId) {
|
||||
const u = await this.prisma.adminUser.findUnique({
|
||||
const admin = await this.prisma.admin.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
_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");
|
||||
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 } });
|
||||
return { success: true };
|
||||
}
|
||||
@@ -101567,12 +101672,12 @@ var require_admin_users_service = __commonJS({
|
||||
async listUserTypes() {
|
||||
const rows = await this.prisma.userType.findMany({
|
||||
orderBy: { id: "asc" },
|
||||
include: { _count: { select: { adminUsers: true, permissions: true } } }
|
||||
include: { _count: { select: { admins: true, adminUsers: true, permissions: true } } }
|
||||
});
|
||||
return rows.map((t) => ({
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
users_count: t._count.adminUsers,
|
||||
users_count: t._count.admins + t._count.adminUsers,
|
||||
permissions_count: t._count.permissions
|
||||
}));
|
||||
}
|
||||
@@ -101645,14 +101750,19 @@ var require_admin_users_service = __commonJS({
|
||||
}
|
||||
/** Legacy `GET users/show` equivalent for the "اطلاعات من" screen. */
|
||||
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() {
|
||||
return [
|
||||
{ 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.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.ACCOUNTANT, title: "\u062D\u0633\u0627\u0628\u062F\u0627\u0631" }
|
||||
];
|
||||
@@ -101915,7 +102025,7 @@ var require_admin_invoices_service = __commonJS({
|
||||
"createdAt",
|
||||
"branchId",
|
||||
"branch.name",
|
||||
"allocatorAdminUser.fullName",
|
||||
"allocatorAdmin.fullName",
|
||||
// legacy filter keys
|
||||
"name",
|
||||
"caj"
|
||||
@@ -101923,7 +102033,7 @@ var require_admin_invoices_service = __commonJS({
|
||||
static FIELD_ALIASES = {
|
||||
name: "branch.name",
|
||||
caj: "createdAt",
|
||||
"allocator_user.name": "allocatorAdminUser.fullName"
|
||||
"allocator_user.name": "allocatorAdmin.fullName"
|
||||
};
|
||||
/** Legacy `POST branch/product/table`. */
|
||||
async list(query) {
|
||||
@@ -101947,7 +102057,7 @@ var require_admin_invoices_service = __commonJS({
|
||||
take,
|
||||
include: {
|
||||
branch: true,
|
||||
allocatorAdminUser: true,
|
||||
allocatorAdmin: true,
|
||||
items: { include: { product: true } }
|
||||
}
|
||||
}),
|
||||
@@ -101966,7 +102076,7 @@ var require_admin_invoices_service = __commonJS({
|
||||
sum_weight: sumWeight,
|
||||
count_item: a.items.length,
|
||||
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,
|
||||
products: a.items.map((it) => ({
|
||||
id: it.productId,
|
||||
@@ -101983,7 +102093,7 @@ var require_admin_invoices_service = __commonJS({
|
||||
where: { id },
|
||||
include: {
|
||||
branch: true,
|
||||
allocatorAdminUser: true,
|
||||
allocatorAdmin: true,
|
||||
items: { include: { product: true } }
|
||||
}
|
||||
});
|
||||
@@ -102017,7 +102127,7 @@ var require_admin_invoices_service = __commonJS({
|
||||
const allocation = await this.prisma.branchAllocation.create({
|
||||
data: {
|
||||
branchId,
|
||||
allocatorAdminUserId: actorId,
|
||||
allocatorAdminId: actorId,
|
||||
note: data.note ?? null,
|
||||
items: {
|
||||
create: products.map((p) => ({
|
||||
@@ -102446,7 +102556,7 @@ var require_admin_misc_service = __commonJS({
|
||||
orderBy,
|
||||
skip,
|
||||
take,
|
||||
include: { sentByAdminUser: true }
|
||||
include: { sentByAdmin: true }
|
||||
}),
|
||||
this.prisma.smsMessage.count({ where })
|
||||
]);
|
||||
@@ -102456,7 +102566,7 @@ var require_admin_misc_service = __commonJS({
|
||||
text: s.body,
|
||||
mobile: s.recipients,
|
||||
sent_count: s.sentCount,
|
||||
sender: s.sentByAdminUser?.fullName ?? null,
|
||||
sender: s.sentByAdmin?.fullName ?? null,
|
||||
caj: s.createdAt
|
||||
})), total, query, page, limit);
|
||||
}
|
||||
@@ -102483,7 +102593,7 @@ var require_admin_misc_service = __commonJS({
|
||||
recipients: recipients.join(","),
|
||||
body,
|
||||
sentCount: recipients.length,
|
||||
sentByAdminUserId: actorId
|
||||
sentByAdminId: actorId
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -102566,7 +102676,7 @@ var require_admin_misc_service = __commonJS({
|
||||
title,
|
||||
body,
|
||||
audience,
|
||||
sentByAdminUserId: actorId,
|
||||
sentByAdminId: actorId,
|
||||
recipients: { create: customers.map((c) => ({ customerId: c.id })) }
|
||||
},
|
||||
include: { _count: { select: { recipients: true } } }
|
||||
@@ -102589,7 +102699,7 @@ var require_admin_misc_service = __commonJS({
|
||||
title,
|
||||
body,
|
||||
audience: client_1.MessageAudience.SINGLE_NUMBER,
|
||||
sentByAdminUserId: actorId,
|
||||
sentByAdminId: actorId,
|
||||
recipients: { create: [{ customerId }] }
|
||||
}
|
||||
});
|
||||
@@ -102599,7 +102709,7 @@ var require_admin_misc_service = __commonJS({
|
||||
const n = await this.prisma.notification.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
sentByAdminUser: true,
|
||||
sentByAdmin: true,
|
||||
recipients: { include: { customer: true } }
|
||||
}
|
||||
});
|
||||
@@ -102611,7 +102721,7 @@ var require_admin_misc_service = __commonJS({
|
||||
body: n.body,
|
||||
text: n.body,
|
||||
audience: n.audience,
|
||||
sender: n.sentByAdminUser?.fullName ?? null,
|
||||
sender: n.sentByAdmin?.fullName ?? null,
|
||||
caj: n.createdAt,
|
||||
recipients: n.recipients.map((r) => ({
|
||||
id: r.id,
|
||||
@@ -102638,7 +102748,7 @@ var require_admin_misc_service = __commonJS({
|
||||
skip,
|
||||
take,
|
||||
include: {
|
||||
sentByAdminUser: true,
|
||||
sentByAdmin: true,
|
||||
_count: { select: { recipients: true } }
|
||||
}
|
||||
}),
|
||||
@@ -102649,7 +102759,7 @@ var require_admin_misc_service = __commonJS({
|
||||
title: n.title,
|
||||
text: n.body,
|
||||
audience: n.audience,
|
||||
sender: n.sentByAdminUser?.fullName ?? null,
|
||||
sender: n.sentByAdmin?.fullName ?? null,
|
||||
total_count: n._count.recipients,
|
||||
caj: n.createdAt
|
||||
})), 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 $$;
|
||||
@@ -1846,7 +1846,7 @@ async function main() {
|
||||
update: {},
|
||||
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 },
|
||||
update: { passwordHash: DEV_TEST_PHONE_PASSWORD_HASH, userTypeId: 85, branchId: null },
|
||||
create: {
|
||||
@@ -1858,7 +1858,7 @@ async function main() {
|
||||
branchId: null
|
||||
}
|
||||
});
|
||||
await prisma.adminUser.upsert({
|
||||
await prisma.admin.upsert({
|
||||
where: { mobile: "09120000001" },
|
||||
update: { passwordHash: PLACEHOLDER_PASSWORD_HASH, userTypeId: 85, branchId: null },
|
||||
create: {
|
||||
@@ -1899,7 +1899,7 @@ async function main() {
|
||||
wagePercent: 1.2
|
||||
}
|
||||
});
|
||||
await prisma.adminUser.upsert({
|
||||
await prisma.admin.upsert({
|
||||
where: { mobile: "09123334444" },
|
||||
update: { passwordHash: PLACEHOLDER_PASSWORD_HASH, userTypeId: 88 },
|
||||
create: {
|
||||
@@ -1910,7 +1910,7 @@ async function main() {
|
||||
userTypeId: 88
|
||||
}
|
||||
});
|
||||
await prisma.adminUser.upsert({
|
||||
await prisma.admin.upsert({
|
||||
where: { mobile: "09125556666" },
|
||||
update: { passwordHash: PLACEHOLDER_PASSWORD_HASH, userTypeId: 87, branchId: null },
|
||||
create: {
|
||||
|
||||
+26
-11
File diff suppressed because one or more lines are too long
+23
-8
@@ -130,6 +130,20 @@ exports.Prisma.CityScalarFieldEnum = {
|
||||
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 = {
|
||||
id: 'id',
|
||||
fullName: 'fullName',
|
||||
@@ -170,7 +184,7 @@ exports.Prisma.AppSessionScalarFieldEnum = {
|
||||
exports.Prisma.AdminSessionScalarFieldEnum = {
|
||||
id: 'id',
|
||||
jti: 'jti',
|
||||
adminUserId: 'adminUserId',
|
||||
adminId: 'adminId',
|
||||
iamSessionId: 'iamSessionId',
|
||||
iamSub: 'iamSub',
|
||||
ipAddress: 'ipAddress',
|
||||
@@ -370,7 +384,7 @@ exports.Prisma.OrderStatusEventScalarFieldEnum = {
|
||||
fromStatus: 'fromStatus',
|
||||
toStatus: 'toStatus',
|
||||
note: 'note',
|
||||
actorAdminUserId: 'actorAdminUserId',
|
||||
actorAdminId: 'actorAdminId',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
@@ -397,7 +411,7 @@ exports.Prisma.PreOrderStatusEventScalarFieldEnum = {
|
||||
id: 'id',
|
||||
preOrderId: 'preOrderId',
|
||||
changes: 'changes',
|
||||
actorAdminUserId: 'actorAdminUserId',
|
||||
actorAdminId: 'actorAdminId',
|
||||
ipAddress: 'ipAddress',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
@@ -414,7 +428,7 @@ exports.Prisma.LedgerEntryScalarFieldEnum = {
|
||||
orderId: 'orderId',
|
||||
confirmed: 'confirmed',
|
||||
confirmedAt: 'confirmedAt',
|
||||
postedByAdminUserId: 'postedByAdminUserId',
|
||||
postedByAdminId: 'postedByAdminId',
|
||||
labName: 'labName',
|
||||
hallmark: 'hallmark',
|
||||
accountName: 'accountName',
|
||||
@@ -549,7 +563,7 @@ exports.Prisma.PromotionExclusionScalarFieldEnum = {
|
||||
exports.Prisma.BranchAllocationScalarFieldEnum = {
|
||||
id: 'id',
|
||||
branchId: 'branchId',
|
||||
allocatorAdminUserId: 'allocatorAdminUserId',
|
||||
allocatorAdminId: 'allocatorAdminId',
|
||||
note: 'note',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
@@ -567,7 +581,7 @@ exports.Prisma.SmsMessageScalarFieldEnum = {
|
||||
recipients: 'recipients',
|
||||
body: 'body',
|
||||
sentCount: 'sentCount',
|
||||
sentByAdminUserId: 'sentByAdminUserId',
|
||||
sentByAdminId: 'sentByAdminId',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
@@ -576,7 +590,7 @@ exports.Prisma.NotificationScalarFieldEnum = {
|
||||
title: 'title',
|
||||
body: 'body',
|
||||
audience: 'audience',
|
||||
sentByAdminUserId: 'sentByAdminUserId',
|
||||
sentByAdminId: 'sentByAdminId',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
@@ -609,7 +623,7 @@ exports.Prisma.UploadedFileScalarFieldEnum = {
|
||||
mimeType: 'mimeType',
|
||||
sizeBytes: 'sizeBytes',
|
||||
purpose: 'purpose',
|
||||
uploadedByAdminUserId: 'uploadedByAdminUserId',
|
||||
uploadedByAdminId: 'uploadedByAdminId',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
@@ -782,6 +796,7 @@ exports.MessageAudience = exports.$Enums.MessageAudience = {
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
City: 'City',
|
||||
Admin: 'Admin',
|
||||
AdminUser: 'AdminUser',
|
||||
AppSession: 'AppSession',
|
||||
AdminSession: 'AdminSession',
|
||||
|
||||
+4830
-3253
File diff suppressed because it is too large
Load Diff
+26
-11
File diff suppressed because one or more lines are too long
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "prisma-client-8945b25ad1891f9eaff8d38ca7f3c1b19a3f2d11c0c8521549c219186ff7566b",
|
||||
"name": "prisma-client-d98bddf3599c950eb3fcd511efa7f226c4ef4c5c3cbc261683fb1118a763a924",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"browser": "index-browser.js",
|
||||
|
||||
+92
-48
@@ -140,6 +140,61 @@ model City {
|
||||
@@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 {
|
||||
id String @id @default(cuid())
|
||||
|
||||
@@ -185,26 +240,13 @@ model AdminUser {
|
||||
pasazhName String? // legacy pasazh_name
|
||||
memberCode String? // legacy mcode
|
||||
|
||||
referredCustomers Customer[] @relation("ReferralAgent")
|
||||
referredCustomers Customer[] @relation("ReferralAgent")
|
||||
/// Orders credited to this sales expert — see Order.salesAgentId.
|
||||
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")
|
||||
creditedOrders Order[] @relation("OrderSalesAgent")
|
||||
|
||||
/// Only ever set for role = SALES_EXPERT (D17).
|
||||
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([userTypeId])
|
||||
@@index([avatarFileId])
|
||||
@@ -274,8 +316,8 @@ model AdminSession {
|
||||
/// The token's `jti` claim. Unique: one row per issued token.
|
||||
jti String @unique
|
||||
|
||||
adminUserId String
|
||||
adminUser AdminUser @relation(fields: [adminUserId], references: [id], onDelete: Cascade)
|
||||
adminId String @map("adminUserId")
|
||||
admin Admin @relation(fields: [adminId], references: [id], onDelete: Cascade)
|
||||
|
||||
/// 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 —
|
||||
@@ -294,7 +336,7 @@ model AdminSession {
|
||||
revokedAt DateTime?
|
||||
revokedReason String?
|
||||
|
||||
@@index([adminUserId])
|
||||
@@index([adminId], map: "AdminSession_adminUserId_idx")
|
||||
@@index([iamSessionId])
|
||||
@@index([iamSub])
|
||||
@@index([expiresAt])
|
||||
@@ -571,6 +613,7 @@ model Branch {
|
||||
cityId String?
|
||||
city City? @relation(fields: [cityId], references: [id])
|
||||
adminUsers AdminUser[]
|
||||
admins Admin[]
|
||||
orders Order[]
|
||||
allocations BranchAllocation[]
|
||||
}
|
||||
@@ -583,7 +626,7 @@ model Workshop {
|
||||
phone String?
|
||||
/// The supervisor created alongside the workshop by the legacy Add form.
|
||||
supervisorId String?
|
||||
supervisor AdminUser? @relation("WorkshopSupervisor", fields: [supervisorId], references: [id])
|
||||
supervisor Admin? @relation("WorkshopSupervisor", fields: [supervisorId], references: [id])
|
||||
isActive Boolean @default(true)
|
||||
preOrders PreOrder[]
|
||||
}
|
||||
@@ -826,15 +869,15 @@ model OrderItem {
|
||||
}
|
||||
|
||||
model OrderStatusEvent {
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id])
|
||||
fromStatus OrderStatus?
|
||||
toStatus OrderStatus
|
||||
note String?
|
||||
actorAdminUserId String?
|
||||
actorAdminUser AdminUser? @relation(fields: [actorAdminUserId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
id String @id @default(cuid())
|
||||
orderId String
|
||||
order Order @relation(fields: [orderId], references: [id])
|
||||
fromStatus OrderStatus?
|
||||
toStatus OrderStatus
|
||||
note String?
|
||||
actorAdminId String? @map("actorAdminUserId")
|
||||
actorAdmin Admin? @relation(fields: [actorAdminId], references: [id])
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([orderId])
|
||||
}
|
||||
@@ -875,15 +918,15 @@ model PreOrder {
|
||||
}
|
||||
|
||||
model PreOrderStatusEvent {
|
||||
id String @id @default(cuid())
|
||||
preOrderId String
|
||||
preOrder PreOrder @relation(fields: [preOrderId], references: [id])
|
||||
id String @id @default(cuid())
|
||||
preOrderId String
|
||||
preOrder PreOrder @relation(fields: [preOrderId], references: [id])
|
||||
/// Field-level diff, e.g. {"status": {"old": "PLACED", "new": "ADMIN_APPROVED"}}
|
||||
changes Json
|
||||
actorAdminUserId String?
|
||||
actorAdminUser AdminUser? @relation(fields: [actorAdminUserId], references: [id])
|
||||
ipAddress String?
|
||||
createdAt DateTime @default(now())
|
||||
changes Json
|
||||
actorAdminId String? @map("actorAdminUserId")
|
||||
actorAdmin Admin? @relation(fields: [actorAdminId], references: [id])
|
||||
ipAddress String?
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@index([preOrderId])
|
||||
}
|
||||
@@ -912,10 +955,10 @@ model LedgerEntry {
|
||||
order Order? @relation(fields: [orderId], references: [id])
|
||||
|
||||
/// Pending entries don't count toward the cached customer balance until confirmed.
|
||||
confirmed Boolean @default(false)
|
||||
confirmedAt DateTime?
|
||||
postedByAdminUserId String?
|
||||
postedByAdminUser AdminUser? @relation(fields: [postedByAdminUserId], references: [id])
|
||||
confirmed Boolean @default(false)
|
||||
confirmedAt DateTime?
|
||||
postedByAdminId String? @map("postedByAdminUserId")
|
||||
postedByAdmin Admin? @relation(fields: [postedByAdminId], references: [id])
|
||||
|
||||
/// Instrument metadata from the legacy "cash" document form. Which fields are
|
||||
/// filled depends on sourceType: a melted-gold receipt carries lab/hallmark and
|
||||
@@ -997,6 +1040,7 @@ model UserType {
|
||||
|
||||
permissions Permission[]
|
||||
adminUsers AdminUser[]
|
||||
admins Admin[]
|
||||
|
||||
@@map("user_type")
|
||||
}
|
||||
@@ -1251,8 +1295,8 @@ model BranchAllocation {
|
||||
branch Branch @relation(fields: [branchId], references: [id])
|
||||
|
||||
/// Legacy `allocator_user` — the admin who filed the allocation.
|
||||
allocatorAdminUserId String?
|
||||
allocatorAdminUser AdminUser? @relation("AllocationAuthor", fields: [allocatorAdminUserId], references: [id])
|
||||
allocatorAdminId String? @map("allocatorAdminUserId")
|
||||
allocatorAdmin Admin? @relation("AllocationAuthor", fields: [allocatorAdminId], references: [id])
|
||||
|
||||
note String?
|
||||
createdAt DateTime @default(now())
|
||||
@@ -1299,8 +1343,8 @@ model SmsMessage {
|
||||
/// How many numbers the send actually resolved to.
|
||||
sentCount Int @default(0)
|
||||
|
||||
sentByAdminUserId String?
|
||||
sentByAdminUser AdminUser? @relation("SmsAuthor", fields: [sentByAdminUserId], references: [id])
|
||||
sentByAdminId String? @map("sentByAdminUserId")
|
||||
sentByAdmin Admin? @relation("SmsAuthor", fields: [sentByAdminId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@ -1313,8 +1357,8 @@ model Notification {
|
||||
body String
|
||||
audience MessageAudience @default(ALL_CUSTOMERS)
|
||||
|
||||
sentByAdminUserId String?
|
||||
sentByAdminUser AdminUser? @relation("NotificationAuthor", fields: [sentByAdminUserId], references: [id])
|
||||
sentByAdminId String? @map("sentByAdminUserId")
|
||||
sentByAdmin Admin? @relation("NotificationAuthor", fields: [sentByAdminId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
|
||||
@@ -1379,8 +1423,8 @@ model UploadedFile {
|
||||
/// 'customer.nationalId'. Kept loose so a new form needs no schema change.
|
||||
purpose String?
|
||||
|
||||
uploadedByAdminUserId String?
|
||||
uploadedByAdminUser AdminUser? @relation("FileUploader", fields: [uploadedByAdminUserId], references: [id])
|
||||
uploadedByAdminId String? @map("uploadedByAdminUserId")
|
||||
uploadedByAdmin Admin? @relation("FileUploader", fields: [uploadedByAdminId], references: [id])
|
||||
|
||||
createdAt DateTime @default(now())
|
||||
customerAvatars Customer[] @relation("CustomerAvatar")
|
||||
|
||||
+23
-8
@@ -130,6 +130,20 @@ exports.Prisma.CityScalarFieldEnum = {
|
||||
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 = {
|
||||
id: 'id',
|
||||
fullName: 'fullName',
|
||||
@@ -170,7 +184,7 @@ exports.Prisma.AppSessionScalarFieldEnum = {
|
||||
exports.Prisma.AdminSessionScalarFieldEnum = {
|
||||
id: 'id',
|
||||
jti: 'jti',
|
||||
adminUserId: 'adminUserId',
|
||||
adminId: 'adminId',
|
||||
iamSessionId: 'iamSessionId',
|
||||
iamSub: 'iamSub',
|
||||
ipAddress: 'ipAddress',
|
||||
@@ -370,7 +384,7 @@ exports.Prisma.OrderStatusEventScalarFieldEnum = {
|
||||
fromStatus: 'fromStatus',
|
||||
toStatus: 'toStatus',
|
||||
note: 'note',
|
||||
actorAdminUserId: 'actorAdminUserId',
|
||||
actorAdminId: 'actorAdminId',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
@@ -397,7 +411,7 @@ exports.Prisma.PreOrderStatusEventScalarFieldEnum = {
|
||||
id: 'id',
|
||||
preOrderId: 'preOrderId',
|
||||
changes: 'changes',
|
||||
actorAdminUserId: 'actorAdminUserId',
|
||||
actorAdminId: 'actorAdminId',
|
||||
ipAddress: 'ipAddress',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
@@ -414,7 +428,7 @@ exports.Prisma.LedgerEntryScalarFieldEnum = {
|
||||
orderId: 'orderId',
|
||||
confirmed: 'confirmed',
|
||||
confirmedAt: 'confirmedAt',
|
||||
postedByAdminUserId: 'postedByAdminUserId',
|
||||
postedByAdminId: 'postedByAdminId',
|
||||
labName: 'labName',
|
||||
hallmark: 'hallmark',
|
||||
accountName: 'accountName',
|
||||
@@ -549,7 +563,7 @@ exports.Prisma.PromotionExclusionScalarFieldEnum = {
|
||||
exports.Prisma.BranchAllocationScalarFieldEnum = {
|
||||
id: 'id',
|
||||
branchId: 'branchId',
|
||||
allocatorAdminUserId: 'allocatorAdminUserId',
|
||||
allocatorAdminId: 'allocatorAdminId',
|
||||
note: 'note',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
@@ -567,7 +581,7 @@ exports.Prisma.SmsMessageScalarFieldEnum = {
|
||||
recipients: 'recipients',
|
||||
body: 'body',
|
||||
sentCount: 'sentCount',
|
||||
sentByAdminUserId: 'sentByAdminUserId',
|
||||
sentByAdminId: 'sentByAdminId',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
@@ -576,7 +590,7 @@ exports.Prisma.NotificationScalarFieldEnum = {
|
||||
title: 'title',
|
||||
body: 'body',
|
||||
audience: 'audience',
|
||||
sentByAdminUserId: 'sentByAdminUserId',
|
||||
sentByAdminId: 'sentByAdminId',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
@@ -609,7 +623,7 @@ exports.Prisma.UploadedFileScalarFieldEnum = {
|
||||
mimeType: 'mimeType',
|
||||
sizeBytes: 'sizeBytes',
|
||||
purpose: 'purpose',
|
||||
uploadedByAdminUserId: 'uploadedByAdminUserId',
|
||||
uploadedByAdminId: 'uploadedByAdminId',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
|
||||
@@ -782,6 +796,7 @@ exports.MessageAudience = exports.$Enums.MessageAudience = {
|
||||
|
||||
exports.Prisma.ModelName = {
|
||||
City: 'City',
|
||||
Admin: 'Admin',
|
||||
AdminUser: 'AdminUser',
|
||||
AppSession: 'AppSession',
|
||||
AdminSession: 'AdminSession',
|
||||
|
||||
Reference in New Issue
Block a user