Initial commit: 风影 OA 全栈源码(API/Web/Flutter/IM)
含 Phase 1.1 IM seq 排序、断线重连、多端已读同步与微信式语音转文字 UI。 排除 node_modules、构建产物、安装包与 .env 密钥。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,466 @@
|
||||
import { Prisma, PrismaClient } from '@prisma/client';
|
||||
import { mapExpenseLineKind, parseOaJson } from './oa-json';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
const FLOW = '/opt/import/prod/oa_flow_finance.json';
|
||||
const FRESH = '/opt/import/prod/oa_fresh.json';
|
||||
|
||||
type Row = Record<string, unknown>;
|
||||
|
||||
function str(v: unknown) {
|
||||
if (v == null) return '';
|
||||
return String(v).trim();
|
||||
}
|
||||
|
||||
function num(v: unknown) {
|
||||
if (v == null || v === '') return 0;
|
||||
const n = Number(String(v).replace(/,/g, ''));
|
||||
return Number.isFinite(n) ? n : 0;
|
||||
}
|
||||
|
||||
function when(v: unknown) {
|
||||
const s = str(v);
|
||||
if (!s || s.startsWith('0000')) return null;
|
||||
const d = new Date(s.includes('T') ? s : s.replace(' ', 'T'));
|
||||
return Number.isNaN(d.getTime()) ? null : d;
|
||||
}
|
||||
|
||||
function clip(v: unknown, n = 200) {
|
||||
const s = str(v);
|
||||
return s.length > n ? s.slice(0, n) : s;
|
||||
}
|
||||
|
||||
function compactName(name: string) {
|
||||
return str(name)
|
||||
.replace(/[()()\[\]【】\s\-—_.,,、]/g, '')
|
||||
.replace(/招标公告|采购项目|技术服务合同|合同/g, '');
|
||||
}
|
||||
|
||||
function namesOverlap(a: string, b: string) {
|
||||
const na = compactName(a);
|
||||
const nb = compactName(b);
|
||||
if (!na || !nb) return false;
|
||||
if (na === nb) return true;
|
||||
const shorter = na.length <= nb.length ? na : nb;
|
||||
const longer = na.length <= nb.length ? nb : na;
|
||||
return shorter.length >= 10 && longer.includes(shorter);
|
||||
}
|
||||
|
||||
function mapProjectType(v: unknown, projectName?: unknown) {
|
||||
const name = str(projectName);
|
||||
if (name.includes('三维')) return '三维';
|
||||
if (/(视频.{0,12}(制作|摄制|拍摄)|摄制|拍摄|摄影|宣传片|专题片|纪录片|微电影|影视制作)/.test(name)) return '摄制';
|
||||
const s = str(v);
|
||||
if (s.includes('三维')) return '三维';
|
||||
if (s.includes('摄制') || s.includes('视频')) return '摄制';
|
||||
if (s.includes('物资') && s.includes('软件')) return '集成';
|
||||
if (s.includes('物资')) return '物资';
|
||||
if (s.includes('集成')) return '集成';
|
||||
if (s === '软件') return '软件';
|
||||
return '软件';
|
||||
}
|
||||
|
||||
function mapDocStatus(stage: number, approval: number) {
|
||||
if (stage === 8 || approval === 3) return 'TERMINATED';
|
||||
if (stage === 1 || stage === 2 || stage === 3 || stage === 4 || stage === 5 || stage === 6) return 'DRAFTING';
|
||||
if (stage === 7 && approval === 2) return 'PENDING';
|
||||
return 'DRAFTING';
|
||||
}
|
||||
|
||||
function mapPreinvestStatus(stage: number, approval: number) {
|
||||
if (stage === 6 || approval === 3) return 'TERMINATED';
|
||||
if (stage === 3) return 'TECH_FINAL';
|
||||
if (stage === 1) return 'TECH_INITIAL';
|
||||
if (stage === 5 && approval === 2) return '';
|
||||
return 'FETCH';
|
||||
}
|
||||
|
||||
function mapExpenseStatus(v: unknown) {
|
||||
const s = str(v);
|
||||
if (s === '2') return 'APPROVED';
|
||||
if (s === '3') return 'REJECTED';
|
||||
if (s === '4') return 'DRAFT';
|
||||
return 'PENDING';
|
||||
}
|
||||
|
||||
function mapLoanStatus(row: Row) {
|
||||
const st = str(row.approval_status);
|
||||
const offset = num(row.offset_amount);
|
||||
if (st === '2') return offset >= num(row.amount) && offset > 0 ? 'OFFSET' : 'OPEN';
|
||||
if (st === '1') return 'PENDING';
|
||||
if (st === '3') return 'REJECTED';
|
||||
return 'DRAFT';
|
||||
}
|
||||
|
||||
function mapItemKind(itemType: string, remark = '') {
|
||||
return mapExpenseLineKind(itemType, remark);
|
||||
}
|
||||
|
||||
function mapItemCategory(itemType: string) {
|
||||
if (itemType.includes('交通') || itemType.includes('差旅') || itemType.includes('机票') || itemType.includes('高铁')) return '交通';
|
||||
if (itemType.includes('餐')) return '餐饮';
|
||||
if (itemType.includes('住宿')) return '住宿';
|
||||
if (itemType.includes('办公')) return '办公';
|
||||
if (itemType.includes('招待')) return '招待';
|
||||
return '其他';
|
||||
}
|
||||
|
||||
function isBidExpense(itemType: string) {
|
||||
return itemType.includes('投标') || itemType.includes('制标') || itemType.includes('取标') || itemType.includes('印');
|
||||
}
|
||||
|
||||
function officialNo(raw: unknown) {
|
||||
const n = str(raw).replace(/[))]+$/, '').trim();
|
||||
if (!n || n === '无' || n === '-' || n === '—') return '';
|
||||
return n;
|
||||
}
|
||||
|
||||
function inferTenderMethod(name: string, stored?: string) {
|
||||
const s = str(stored);
|
||||
if (s) return s;
|
||||
const n = name || '';
|
||||
if (n.includes('询价')) return '询价';
|
||||
if (n.includes('邀请招标')) return '邀请招标';
|
||||
if (n.includes('单一来源')) return '单一来源';
|
||||
if (n.includes('竞谈最低') || n.includes('竞争性谈判(最低') || n.includes('竞争性谈判(最低')) return '竞谈最低价';
|
||||
if (n.includes('竞谈综合') || n.includes('综合评审')) return '竞谈综合评审';
|
||||
if (n.includes('竞争性谈判') || n.includes('竞谈')) return '竞争性谈判';
|
||||
if (n.includes('竞争性磋商') || n.includes('磋商')) return '竞争性磋商';
|
||||
if (n.includes('公开招标')) return '公开招标';
|
||||
return '';
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const flow = parseOaJson<Record<string, Row[]>>(FLOW);
|
||||
const fresh = parseOaJson<Record<string, Row[]>>(FRESH);
|
||||
const admin = await prisma.user.findFirst({ where: { username: 'admin' } });
|
||||
if (!admin) throw new Error('本地没有 admin');
|
||||
const entity =
|
||||
(await prisma.legalEntity.findFirst({ where: { tenantId: '1', isDefault: true } })) ||
|
||||
(await prisma.legalEntity.findFirst({ where: { tenantId: '1' } }));
|
||||
if (!entity) throw new Error('本地没有主体');
|
||||
|
||||
const users = await prisma.user.findMany({ select: { id: true, displayName: true, username: true } });
|
||||
const userByName = new Map(users.map((u) => [u.displayName, u.id]));
|
||||
userByName.set('张靖妍', userByName.get('张婧妍') || '');
|
||||
|
||||
const entities = await prisma.legalEntity.findMany();
|
||||
const entityByName = new Map(entities.map((e) => [e.name, e.id]));
|
||||
for (const e of entities) if (e.shortName) entityByName.set(e.shortName, e.id);
|
||||
|
||||
const projects = await prisma.project.findMany({ select: { id: true, name: true, projectNo: true } });
|
||||
const projectByName = new Map(projects.map((p) => [p.name, p.id]));
|
||||
const projectByOaId = new Map<string, string>();
|
||||
for (const p of fresh.project_list || []) {
|
||||
const local = projects.find((x) => x.projectNo === str(p.system_number) || namesOverlap(x.name, str(p.project_name)));
|
||||
if (local && p.project_id != null) projectByOaId.set(String(p.project_id), local.id);
|
||||
}
|
||||
|
||||
const tenders = (fresh.oa_tender || []).filter((t) => /^TB-\d/.test(str(t.system_number)));
|
||||
const tenderById = new Map(tenders.map((t) => [String(t.id), t]));
|
||||
|
||||
const allPreinvests = flow.project_preinvest_apply || [];
|
||||
const preByTask = new Map<string, Row>();
|
||||
const preByTender = new Map<string, Row>();
|
||||
const preByName = new Map<string, Row>();
|
||||
for (const p of [...allPreinvests].sort((a, b) => num(a.deleted) - num(b.deleted))) {
|
||||
if (p.bid_document_task_id != null) preByTask.set(String(p.bid_document_task_id), p);
|
||||
if (p.tender_id != null) preByTender.set(String(p.tender_id), p);
|
||||
if (str(p.project_name) && !preByName.has(str(p.project_name))) preByName.set(str(p.project_name), p);
|
||||
}
|
||||
|
||||
const bids = await prisma.bidCase.findMany();
|
||||
const usedNos = new Set(bids.map((b) => b.bidNo));
|
||||
|
||||
function findBid(_name: string, nos: string[]) {
|
||||
const wanted = new Set(nos.filter(Boolean));
|
||||
if (!wanted.size) return undefined;
|
||||
return bids.find((b) => wanted.has(b.bidNo) || (b.externalNo && wanted.has(b.externalNo)));
|
||||
}
|
||||
|
||||
let applyPatched = 0;
|
||||
for (const t of tenders) {
|
||||
const pre = preByTender.get(String(t.id)) || preByName.get(str(t.project_name));
|
||||
const start = when(pre?.file_apply_time_start) || when(t.bid_prepare_date) || when(t.create_time);
|
||||
const end = when(pre?.file_apply_time_end) || when(t.file_apply_deadline);
|
||||
const row = findBid(str(t.project_name), [officialNo(t.project_number), str(t.system_number)]);
|
||||
if (!row || (!start && !end)) continue;
|
||||
await prisma.bidCase.update({
|
||||
where: { id: row.id },
|
||||
data: {
|
||||
applyStartAt: start || row.applyStartAt,
|
||||
applyEndAt: end || row.applyEndAt,
|
||||
applyMethod: clip(pre?.apply_method, 50) || row.applyMethod,
|
||||
openMethod: clip(pre?.bid_open_method, 50) || row.openMethod,
|
||||
},
|
||||
});
|
||||
applyPatched += 1;
|
||||
}
|
||||
|
||||
async function ensureBid(opts: {
|
||||
bidNo: string;
|
||||
name: string;
|
||||
official?: string;
|
||||
status: string;
|
||||
openAt?: Date | null;
|
||||
openPlace?: string;
|
||||
applyStart?: Date | null;
|
||||
applyEnd?: Date | null;
|
||||
applyMethod?: string;
|
||||
openMethod?: string;
|
||||
projectType?: string;
|
||||
source?: string;
|
||||
qualification?: string;
|
||||
entityName?: string;
|
||||
createdAt?: Date | null;
|
||||
createdByName?: string;
|
||||
tenderMethod?: string;
|
||||
}) {
|
||||
if (!opts.name || /^test/i.test(opts.name)) return 'skip';
|
||||
const existing = findBid(opts.name, [opts.bidNo, opts.official || '']);
|
||||
if (existing) {
|
||||
const result = ['WON', 'LOST', 'FAILED'].includes(existing.status);
|
||||
const sameNo = existing.bidNo === opts.bidNo;
|
||||
if (opts.status === 'TERMINATED' && !sameNo && !result && existing.status !== 'TERMINATED') {
|
||||
// 同名在途单不因回收站记录被误杀,下面另建终止单
|
||||
} else {
|
||||
const nextStatus = result ? existing.status : opts.status === 'TERMINATED' || existing.status === 'TERMINATED' ? 'TERMINATED' : opts.status;
|
||||
await prisma.bidCase.update({
|
||||
where: { id: existing.id },
|
||||
data: {
|
||||
applyStartAt: opts.applyStart || existing.applyStartAt,
|
||||
applyEndAt: opts.applyEnd || existing.applyEndAt,
|
||||
applyMethod: opts.applyMethod || existing.applyMethod,
|
||||
openMethod: opts.openMethod || existing.openMethod,
|
||||
openAt: opts.openAt || existing.openAt,
|
||||
openPlace: opts.openPlace || existing.openPlace,
|
||||
projectType: mapProjectType(opts.projectType, opts.name) || existing.projectType,
|
||||
tenderMethod: inferTenderMethod(opts.name, opts.tenderMethod) || existing.tenderMethod,
|
||||
status: nextStatus,
|
||||
},
|
||||
});
|
||||
return 'updated';
|
||||
}
|
||||
}
|
||||
let bidNo = opts.bidNo;
|
||||
if (!bidNo || usedNos.has(bidNo)) bidNo = opts.official || opts.bidNo;
|
||||
if (usedNos.has(bidNo)) bidNo = `${opts.bidNo}-${Date.now().toString().slice(-4)}`;
|
||||
usedNos.add(bidNo);
|
||||
const created = await prisma.bidCase.create({
|
||||
data: {
|
||||
bidNo,
|
||||
name: clip(opts.name, 200),
|
||||
externalNo: opts.official || undefined,
|
||||
projectType: mapProjectType(opts.projectType, opts.name),
|
||||
sourceType: 'FLOW',
|
||||
source: clip(opts.source, 50) || undefined,
|
||||
qualificationNeed: clip(opts.qualification, 40) || undefined,
|
||||
legalEntityId: (opts.entityName && entityByName.get(opts.entityName)) || entity.id,
|
||||
applyStartAt: opts.applyStart || undefined,
|
||||
applyEndAt: opts.applyEnd || undefined,
|
||||
applyMethod: clip(opts.applyMethod, 50) || undefined,
|
||||
openAt: opts.openAt || undefined,
|
||||
openMethod: clip(opts.openMethod, 50) || undefined,
|
||||
openPlace: clip(opts.openPlace, 200) || undefined,
|
||||
tenderMethod: inferTenderMethod(opts.name, opts.tenderMethod) || undefined,
|
||||
status: opts.status,
|
||||
createdById: userByName.get(opts.createdByName || '') || admin.id,
|
||||
createdAt: opts.createdAt || undefined,
|
||||
},
|
||||
});
|
||||
bids.push(created);
|
||||
return 'created';
|
||||
}
|
||||
|
||||
const created = { drafting: 0, screening: 0, terminated: 0, other: 0, updated: 0, skipped: 0 };
|
||||
const tasks = (flow.bid_document_task || []).filter((t) => str(t.project_name));
|
||||
for (const t of tasks) {
|
||||
const pre = preByTask.get(String(t.id)) || preByName.get(str(t.project_name));
|
||||
const tender = t.tender_id != null ? tenderById.get(String(t.tender_id)) : undefined;
|
||||
const deleted = num(t.is_deleted) === 1;
|
||||
const status = deleted
|
||||
? 'TERMINATED'
|
||||
: tender
|
||||
? undefined
|
||||
: mapDocStatus(num(t.workflow_stage), num(t.approval_status));
|
||||
if (!status && tender) {
|
||||
const r = await ensureBid({
|
||||
bidNo: str(t.task_no),
|
||||
name: str(t.project_name),
|
||||
official: officialNo(t.project_number) || officialNo(tender.project_number),
|
||||
status: 'PENDING',
|
||||
openAt: when(t.bid_open_time) || when(tender.bid_open_date),
|
||||
openPlace: clip(t.bid_open_location, 200),
|
||||
applyStart: when(pre?.file_apply_time_start) || when(tender.bid_prepare_date),
|
||||
applyEnd: when(pre?.file_apply_time_end) || when(t.file_apply_time_end) || when(tender.file_apply_deadline),
|
||||
applyMethod: str(pre?.apply_method),
|
||||
openMethod: str(pre?.bid_open_method),
|
||||
projectType: str(pre?.screening_category || t.screening_category || t.project_type || tender.project_type),
|
||||
source: str(t.project_source || pre?.project_source),
|
||||
qualification: str(pre?.qualification_require),
|
||||
entityName: str(pre?.bid_entity),
|
||||
createdAt: when(t.created_time),
|
||||
createdByName: str(t.dispatcher_name),
|
||||
tenderMethod: str(t.bid_method || tender.tender_method),
|
||||
});
|
||||
if (r === 'updated') created.updated += 1;
|
||||
else if (r === 'skip') created.skipped += 1;
|
||||
continue;
|
||||
}
|
||||
const next = status || 'DRAFTING';
|
||||
const r = await ensureBid({
|
||||
bidNo: str(t.task_no),
|
||||
name: str(t.project_name),
|
||||
official: officialNo(t.project_number),
|
||||
status: next,
|
||||
openAt: when(t.bid_open_time),
|
||||
openPlace: clip(t.bid_open_location, 200),
|
||||
applyStart: when(pre?.file_apply_time_start),
|
||||
applyEnd: when(pre?.file_apply_time_end) || when(t.file_apply_time_end),
|
||||
applyMethod: str(pre?.apply_method),
|
||||
openMethod: str(pre?.bid_open_method),
|
||||
projectType: str(pre?.screening_category || t.screening_category || t.project_type || pre?.project_type),
|
||||
source: str(t.project_source || pre?.project_source),
|
||||
qualification: str(pre?.qualification_require),
|
||||
entityName: str(pre?.bid_entity),
|
||||
createdAt: when(t.created_time),
|
||||
createdByName: str(t.dispatcher_name),
|
||||
tenderMethod: str(t.bid_method),
|
||||
});
|
||||
if (r === 'created') {
|
||||
if (next === 'DRAFTING') created.drafting += 1;
|
||||
else if (next === 'TERMINATED') created.terminated += 1;
|
||||
else created.other += 1;
|
||||
} else if (r === 'updated') created.updated += 1;
|
||||
else created.skipped += 1;
|
||||
}
|
||||
|
||||
for (const p of allPreinvests) {
|
||||
if (!str(p.project_name) || !str(p.apply_no)) continue;
|
||||
const deleted = num(p.deleted) === 1;
|
||||
const status = deleted ? 'TERMINATED' : mapPreinvestStatus(num(p.workflow_stage), num(p.approval_status));
|
||||
if (!status) continue;
|
||||
const r = await ensureBid({
|
||||
bidNo: str(p.apply_no),
|
||||
name: str(p.project_name),
|
||||
official: officialNo(p.project_number),
|
||||
status,
|
||||
openAt: when(p.bid_open_time),
|
||||
openPlace: clip(p.bid_open_location, 200),
|
||||
applyStart: when(p.file_apply_time_start),
|
||||
applyEnd: when(p.file_apply_time_end),
|
||||
applyMethod: str(p.apply_method),
|
||||
openMethod: str(p.bid_open_method),
|
||||
projectType: str(p.screening_category || p.project_type),
|
||||
source: str(p.project_source || p.entry_source),
|
||||
qualification: str(p.qualification_require),
|
||||
entityName: str(p.bid_entity),
|
||||
createdAt: when(p.created_time),
|
||||
createdByName: str(p.applicant_name),
|
||||
tenderMethod: str(p.bid_method),
|
||||
});
|
||||
if (r === 'created') {
|
||||
if (status === 'TERMINATED') created.terminated += 1;
|
||||
else created.screening += 1;
|
||||
} else if (r === 'updated') created.updated += 1;
|
||||
else created.skipped += 1;
|
||||
}
|
||||
|
||||
const itemsByReimb = new Map<string, Row[]>();
|
||||
for (const item of flow.reimbursement_item || []) {
|
||||
const key = String(item.reimbursement_id);
|
||||
if (!itemsByReimb.has(key)) itemsByReimb.set(key, []);
|
||||
itemsByReimb.get(key)!.push(item);
|
||||
}
|
||||
|
||||
let expensesUpserted = 0;
|
||||
for (const r of flow.reimbursement || []) {
|
||||
const claimNo = str(r.reimburse_no);
|
||||
if (!claimNo) continue;
|
||||
const lines = itemsByReimb.get(String(r.id)) || [];
|
||||
const main = [...lines].sort((a, b) => num(b.amount) - num(a.amount))[0];
|
||||
const itemType = str(main?.item_type);
|
||||
const applicantId = userByName.get(str(r.employee_name)) || admin.id;
|
||||
const payerId = userByName.get(str(r.payer_name)) || undefined;
|
||||
const projectId = r.project_id != null ? projectByOaId.get(String(r.project_id)) : projectByName.get(str(r.project_name));
|
||||
const bidHit = findBid(str(r.project_name), []);
|
||||
const bidCaseId = isBidExpense(itemType) || (!projectId && bidHit) ? bidHit?.id : undefined;
|
||||
const costType = bidCaseId ? 'BID' : projectId ? 'PROJECT' : 'DEPT';
|
||||
const data = {
|
||||
applicantId,
|
||||
costType,
|
||||
budgetCategory: costType === 'PROJECT' ? 'TRAVEL' : undefined,
|
||||
projectId: costType === 'PROJECT' ? projectId : undefined,
|
||||
bidCaseId: costType === 'BID' ? bidCaseId : undefined,
|
||||
expenseCategory: mapItemCategory(itemType),
|
||||
purpose: clip(r.subject || r.project_name, 200) || undefined,
|
||||
amount: new Prisma.Decimal(num(r.total_amount)),
|
||||
status: mapExpenseStatus(r.approval_status),
|
||||
remark: clip(r.remark, 1000) || undefined,
|
||||
submittedAt: when(r.created_time) || undefined,
|
||||
decidedAt: str(r.approval_status) === '2' ? when(r.updated_time) || when(r.created_time) : undefined,
|
||||
decidedById: str(r.approval_status) === '2' ? admin.id : undefined,
|
||||
prepaid: Boolean(payerId && payerId !== applicantId),
|
||||
payerId: payerId && payerId !== applicantId ? payerId : undefined,
|
||||
};
|
||||
const existed = await prisma.expenseClaim.findFirst({ where: { tenantId: '1', claimNo } });
|
||||
const row = existed
|
||||
? await prisma.expenseClaim.update({ where: { id: existed.id }, data })
|
||||
: await prisma.expenseClaim.create({ data: { claimNo, ...data } });
|
||||
await prisma.expenseLine.deleteMany({ where: { claimId: row.id } });
|
||||
for (const [i, line] of lines.entries()) {
|
||||
await prisma.expenseLine.create({
|
||||
data: {
|
||||
claimId: row.id,
|
||||
kind: mapItemKind(str(line.item_type), str(line.remark)),
|
||||
amount: new Prisma.Decimal(num(line.amount)),
|
||||
invoiceNo: clip(line.invoice_no, 80) || undefined,
|
||||
occurAt: when(line.expense_date) || undefined,
|
||||
remark: clip(line.remark || line.item_type, 200) || undefined,
|
||||
sortNo: num(line.sort_order) || i,
|
||||
},
|
||||
});
|
||||
}
|
||||
expensesUpserted += 1;
|
||||
}
|
||||
|
||||
let loansUpserted = 0;
|
||||
for (const r of flow.borrow_advance || []) {
|
||||
const loanNo = str(r.advance_no);
|
||||
if (!loanNo) continue;
|
||||
const applicantId = userByName.get(str(r.employee_name)) || admin.id;
|
||||
const projectId = r.project_id != null ? projectByOaId.get(String(r.project_id)) : projectByName.get(str(r.project_name));
|
||||
const data = {
|
||||
applicantId,
|
||||
costType: projectId ? 'PROJECT' : 'DEPT',
|
||||
projectId,
|
||||
amount: new Prisma.Decimal(num(r.amount)),
|
||||
offsetAmount: new Prisma.Decimal(num(r.offset_amount)),
|
||||
purpose: clip(r.reason || r.subject, 200) || 'OA 借款',
|
||||
status: mapLoanStatus(r),
|
||||
submittedAt: when(r.apply_time) || when(r.created_time) || undefined,
|
||||
};
|
||||
const existed = await prisma.loanRecord.findFirst({ where: { tenantId: '1', loanNo } });
|
||||
if (existed) await prisma.loanRecord.update({ where: { id: existed.id }, data });
|
||||
else await prisma.loanRecord.create({ data: { loanNo, ...data } });
|
||||
loansUpserted += 1;
|
||||
}
|
||||
|
||||
const bidCounts = await prisma.bidCase.groupBy({ by: ['status'], _count: true });
|
||||
const expCounts = await prisma.expenseClaim.groupBy({ by: ['status'], _count: true });
|
||||
const loanCounts = await prisma.loanRecord.groupBy({ by: ['status'], _count: true });
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{ applyPatched, created, expensesUpserted, loansUpserted, bidCounts, expCounts, loanCounts },
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(() => prisma.$disconnect());
|
||||
Reference in New Issue
Block a user