imports.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710
  1. 'use server';
  2. import { prisma } from '@/lib/prisma';
  3. import { revalidatePath } from 'next/cache';
  4. import { z } from 'zod';
  5. import { ImportProcessor } from '@/app/lib/excel-import/import-processor';
  6. // Validation schemas
  7. const createImportSchema = z.object({
  8. name: z.string().min(1, 'Import name is required'),
  9. layoutId: z.number().int().positive('Layout configuration is required'),
  10. fileId: z.string().optional(),
  11. });
  12. const updateImportSchema = z.object({
  13. id: z.number().int().positive(),
  14. name: z.string().min(1, 'Import name is required'),
  15. fileId: z.string().optional(),
  16. });
  17. // Create a new import
  18. export async function createImport(data: {
  19. name: string;
  20. layoutId: number;
  21. fileId?: string;
  22. }) {
  23. try {
  24. const validatedData = createImportSchema.parse(data);
  25. const importRecord = await prisma.import.create({
  26. data: {
  27. name: validatedData.name,
  28. layoutId: validatedData.layoutId,
  29. importDate: new Date(),
  30. ...(validatedData.fileId && { fileId: validatedData.fileId }),
  31. },
  32. include: {
  33. layout: true,
  34. },
  35. });
  36. revalidatePath('/imports');
  37. return { success: true, data: importRecord };
  38. } catch (error) {
  39. console.error('Error creating import:', error);
  40. return { success: false, error: 'Failed to create import' };
  41. }
  42. }
  43. // Get all imports for the current user
  44. export async function getImports(userId?: string) {
  45. try {
  46. const imports = await prisma.import.findMany({
  47. where: userId ? {
  48. fileId: {
  49. not: null,
  50. },
  51. } : {},
  52. include: {
  53. layout: true,
  54. },
  55. orderBy: {
  56. importDate: 'desc',
  57. },
  58. });
  59. // Filter by userId manually since Prisma doesn't have direct relation
  60. let filteredImports = imports;
  61. if (userId) {
  62. const fileIds = await prisma.file.findMany({
  63. where: {
  64. userId: userId,
  65. },
  66. select: {
  67. id: true,
  68. },
  69. });
  70. const userFileIds = new Set(fileIds.map(f => f.id));
  71. filteredImports = imports.filter(imp =>
  72. imp.fileId && userFileIds.has(imp.fileId)
  73. );
  74. }
  75. return { success: true, data: filteredImports };
  76. } catch (error) {
  77. console.error('Error fetching imports:', error);
  78. return { success: false, error: 'Failed to fetch imports' };
  79. }
  80. }
  81. // Get a single import by ID
  82. export async function getImportById(id: number) {
  83. try {
  84. const importRecord = await prisma.import.findUnique({
  85. where: { id },
  86. include: {
  87. layout: {
  88. include: {
  89. sections: {
  90. include: {
  91. fields: true,
  92. },
  93. },
  94. },
  95. },
  96. },
  97. });
  98. if (!importRecord) {
  99. return { success: false, error: 'Import not found' };
  100. }
  101. // Fetch file separately if fileId exists
  102. let file = undefined;
  103. if (importRecord.fileId) {
  104. file = await prisma.file.findUnique({
  105. where: { id: importRecord.fileId },
  106. });
  107. }
  108. return { success: true, data: { ...importRecord, file } };
  109. } catch (error) {
  110. console.error('Error fetching import:', error);
  111. return { success: false, error: 'Failed to fetch import' };
  112. }
  113. }
  114. // Update an import
  115. export async function updateImport(data: {
  116. id: number;
  117. name: string;
  118. fileId?: string;
  119. }) {
  120. try {
  121. const validatedData = updateImportSchema.parse(data);
  122. const importRecord = await prisma.import.update({
  123. where: { id: validatedData.id },
  124. data: {
  125. name: validatedData.name,
  126. ...(validatedData.fileId !== undefined && { fileId: validatedData.fileId }),
  127. },
  128. include: {
  129. layout: true,
  130. },
  131. });
  132. revalidatePath('/imports');
  133. return { success: true, data: importRecord };
  134. } catch (error) {
  135. console.error('Error updating import:', error);
  136. return { success: false, error: 'Failed to update import' };
  137. }
  138. }
  139. // Delete an import
  140. export async function deleteImport(id: number) {
  141. try {
  142. await prisma.import.delete({
  143. where: { id },
  144. });
  145. revalidatePath('/imports');
  146. return { success: true };
  147. } catch (error) {
  148. console.error('Error deleting import:', error);
  149. return { success: false, error: 'Failed to delete import' };
  150. }
  151. }
  152. // Calculate Cintas summaries for an import
  153. export async function calculateCintasSummaries(importId: number) {
  154. try {
  155. // This would typically call a stored procedure or perform calculations
  156. // For now, we'll simulate the calculation
  157. // In a real implementation, you might call:
  158. // await prisma.$executeRaw`CALL cintas_calculate_summary(${importId})`;
  159. // For demo purposes, we'll create some sample data
  160. const summaries = [
  161. {
  162. importId,
  163. week: '2024-W01',
  164. trrTotal: 100,
  165. fourWkAverages: 95,
  166. trrPlus4Wk: 195,
  167. powerAdds: 25,
  168. weekId: 1,
  169. },
  170. {
  171. importId,
  172. week: '2024-W02',
  173. trrTotal: 110,
  174. fourWkAverages: 100,
  175. trrPlus4Wk: 210,
  176. powerAdds: 30,
  177. weekId: 2,
  178. },
  179. ];
  180. // Clear existing summaries for this import
  181. await prisma.cintasSummary.deleteMany({
  182. where: { importId },
  183. });
  184. // Create new summaries
  185. const createdSummaries = await Promise.all(
  186. summaries.map(summary =>
  187. prisma.cintasSummary.create({
  188. data: summary,
  189. })
  190. )
  191. );
  192. return { success: true, data: createdSummaries };
  193. } catch (error) {
  194. console.error('Error calculating Cintas summaries:', error);
  195. return { success: false, error: 'Failed to calculate summaries' };
  196. }
  197. }
  198. // Get available layout configurations
  199. export async function getLayoutConfigurations() {
  200. try {
  201. const layouts = await prisma.layoutConfiguration.findMany({
  202. include: {
  203. sections: {
  204. include: {
  205. fields: true,
  206. },
  207. },
  208. },
  209. orderBy: {
  210. name: 'asc',
  211. },
  212. });
  213. return { success: true, data: layouts };
  214. } catch (error) {
  215. console.error('Error fetching layout configurations:', error);
  216. return { success: false, error: 'Failed to fetch layout configurations' };
  217. }
  218. }
  219. // Get import summary (GET /api/imports/[id]/summary)
  220. export async function getImportSummary(importId: number) {
  221. try {
  222. if (!importId || isNaN(importId)) {
  223. return { success: false, error: 'Invalid import ID' };
  224. }
  225. // Check if import exists
  226. const importRecord = await prisma.import.findUnique({
  227. where: { id: importId }
  228. });
  229. if (!importRecord) {
  230. return { success: false, error: 'Import not found' };
  231. }
  232. // Get basic summary data
  233. const totalRecords = await prisma.cintasInstallCalendar.count({
  234. where: { importId }
  235. });
  236. const cintasSummaries = await prisma.cintasSummary.findMany({
  237. where: { importId },
  238. orderBy: { weekId: 'desc' }
  239. });
  240. // Get file info
  241. const file = importRecord.fileId ? await prisma.file.findUnique({
  242. where: { id: importRecord.fileId }
  243. }) : null;
  244. const summary = {
  245. totalRecords,
  246. totalWeeks: cintasSummaries.length,
  247. cintasSummaries: cintasSummaries.map((summary: any) => ({
  248. id: summary.id,
  249. week: summary.week,
  250. trrTotal: summary.trrTotal,
  251. fourWkAverages: summary.fourWkAverages,
  252. trrPlus4Wk: summary.trrPlus4Wk,
  253. powerAdds: summary.powerAdds,
  254. weekId: summary.weekId
  255. }))
  256. };
  257. return {
  258. success: true,
  259. data: {
  260. importId,
  261. fileName: file?.filename || 'Unknown',
  262. uploadDate: importRecord.createdAt,
  263. summary,
  264. summaryExists: cintasSummaries.length > 0
  265. }
  266. };
  267. } catch (error) {
  268. console.error('Error fetching import summary:', error);
  269. return { success: false, error: 'Failed to fetch import summary' };
  270. }
  271. }
  272. // Generate import summary (POST /api/imports/[id]/summary)
  273. export async function generateImportSummary(importId: number) {
  274. try {
  275. if (!importId || isNaN(importId)) {
  276. return { success: false, error: 'Invalid import ID' };
  277. }
  278. // Check if import exists
  279. const importRecord = await prisma.import.findUnique({
  280. where: { id: importId }
  281. });
  282. if (!importRecord) {
  283. return { success: false, error: 'Import not found' };
  284. }
  285. // Check if summary already exists
  286. const existingSummaries = await prisma.cintasSummary.count({
  287. where: { importId }
  288. });
  289. if (existingSummaries > 0) {
  290. // Return existing summary
  291. const cintasSummaries = await prisma.cintasSummary.findMany({
  292. where: { importId },
  293. orderBy: { weekId: 'desc' }
  294. });
  295. return {
  296. success: true,
  297. data: {
  298. importId,
  299. summaryGenerated: false,
  300. message: 'Summary already exists',
  301. summary: cintasSummaries.map((summary: any) => ({
  302. id: summary.id,
  303. week: summary.week,
  304. trrTotal: summary.trrTotal,
  305. fourWkAverages: summary.fourWkAverages,
  306. trrPlus4Wk: summary.trrPlus4Wk,
  307. powerAdds: summary.powerAdds,
  308. weekId: summary.weekId
  309. }))
  310. }
  311. };
  312. }
  313. // Generate new summary using stored procedure
  314. await prisma.$executeRawUnsafe(
  315. `CALL cintas_calculate_summary(${importId})`
  316. );
  317. // Fetch the newly generated summary
  318. const cintasSummaries = await prisma.cintasSummary.findMany({
  319. where: { importId },
  320. orderBy: { weekId: 'desc' }
  321. });
  322. return {
  323. success: true,
  324. data: {
  325. importId,
  326. summaryGenerated: true,
  327. message: 'Summary generated successfully',
  328. summary: cintasSummaries.map((summary: any) => ({
  329. id: summary.id,
  330. week: summary.week,
  331. trrTotal: summary.trrTotal,
  332. fourWkAverages: summary.fourWkAverages,
  333. trrPlus4Wk: summary.trrPlus4Wk,
  334. powerAdds: summary.powerAdds,
  335. weekId: summary.weekId
  336. }))
  337. }
  338. };
  339. } catch (error) {
  340. console.error('Error generating summary:', error);
  341. return { success: false, error: 'Failed to generate summary' };
  342. }
  343. }
  344. // Get import progress (GET /api/imports/[id]/progress)
  345. export async function getImportProgress(importId: number) {
  346. try {
  347. if (!importId || isNaN(importId)) {
  348. return { success: false, error: 'Invalid import ID' };
  349. }
  350. // Check if import exists
  351. const importRecord = await prisma.import.findUnique({
  352. where: { id: importId }
  353. });
  354. if (!importRecord) {
  355. return { success: false, error: 'Import not found' };
  356. }
  357. // Check all possible tables for records
  358. const [cintasCount, gowDataCount, gowFacCount, gowCorpCount] = await Promise.all([
  359. prisma.cintasInstallCalendar.count({ where: { importId } }),
  360. prisma.gowData.count({ where: { importId } }),
  361. prisma.gowFacId.count({ where: { importId } }),
  362. prisma.gowCorpRef.count({ where: { importId } }),
  363. ]);
  364. const totalRecords = cintasCount + gowDataCount + gowFacCount + gowCorpCount;
  365. // Since we don't have status fields, we'll use record count as proxy
  366. const hasRecords = totalRecords > 0;
  367. return {
  368. success: true,
  369. data: {
  370. importId,
  371. status: hasRecords ? 'completed' : 'pending',
  372. progress: hasRecords ? 100 : 0,
  373. processedRecords: totalRecords,
  374. totalRecords: totalRecords,
  375. errorMessage: null,
  376. lastUpdated: importRecord.updatedAt,
  377. timestamp: new Date().toISOString()
  378. }
  379. };
  380. } catch (error) {
  381. console.error('Error fetching import progress:', error);
  382. return { success: false, error: 'Failed to fetch import progress' };
  383. }
  384. }
  385. // Trigger import process (POST /api/imports/[id]/trigger)
  386. export async function triggerImportProcess(importId: number) {
  387. try {
  388. if (!importId || isNaN(importId)) {
  389. return { success: false, error: 'Invalid import ID' };
  390. }
  391. // Validate import exists
  392. const importRecord = await prisma.import.findUnique({
  393. where: { id: importId }
  394. });
  395. if (!importRecord) {
  396. return { success: false, error: 'Import not found' };
  397. }
  398. if (!importRecord.fileId) {
  399. return { success: false, error: 'No file attached to import' };
  400. }
  401. // Check if layout exists
  402. const layout = await prisma.layoutConfiguration.findUnique({
  403. where: { id: importRecord.layoutId }
  404. });
  405. if (!layout) {
  406. return { success: false, error: 'No layout configuration found' };
  407. }
  408. // Check if data already exists for this import
  409. const existingRecords = await prisma.cintasInstallCalendar.count({
  410. where: { importId }
  411. });
  412. if (existingRecords > 0) {
  413. return {
  414. success: true,
  415. message: 'Import already processed',
  416. importId,
  417. existingRecords
  418. };
  419. }
  420. // Use the ImportProcessor to actually process the import
  421. const processor = new ImportProcessor();
  422. const result = await processor.processImport(importId);
  423. if (result.success) {
  424. revalidatePath('/imports');
  425. return {
  426. success: true,
  427. message: 'Import process completed successfully',
  428. importId,
  429. totalInserted: result.totalInserted
  430. };
  431. } else {
  432. return {
  433. success: false,
  434. error: 'Import processing failed',
  435. errors: result.errors
  436. };
  437. }
  438. } catch (error) {
  439. console.error('Error triggering import:', error);
  440. return { success: false, error: 'Failed to trigger import' };
  441. }
  442. }
  443. // Update import progress (for internal use by ImportProcessor)
  444. export async function updateImportProgress(
  445. importId: number,
  446. progress: {
  447. processedRecords: number;
  448. totalRecords: number;
  449. status: string;
  450. errorMessage?: string;
  451. }
  452. ) {
  453. try {
  454. // Since the Import model doesn't have these fields, we'll just return success
  455. // In a real implementation, you would need to add these fields to the schema
  456. console.log(`Import ${importId} progress: ${progress.processedRecords}/${progress.totalRecords} (${progress.status})`);
  457. return { success: true };
  458. } catch (error) {
  459. console.error('Error updating import progress:', error);
  460. return { success: false, error: 'Failed to update progress' };
  461. }
  462. }
  463. // Get imports filtered by layout configuration name
  464. export async function getImportsByLayoutName(layoutName: string) {
  465. try {
  466. // First find the layout by name
  467. const layout = await prisma.layoutConfiguration.findFirst({
  468. where: { name: layoutName },
  469. });
  470. if (!layout) {
  471. return { success: true, data: [] };
  472. }
  473. const imports = await prisma.import.findMany({
  474. where: {
  475. layoutId: layout.id,
  476. },
  477. include: {
  478. layout: true,
  479. },
  480. orderBy: {
  481. importDate: 'desc',
  482. },
  483. });
  484. return { success: true, data: imports };
  485. } catch (error) {
  486. console.error('Error fetching imports by layout:', error);
  487. return { success: false, error: 'Failed to fetch imports' };
  488. }
  489. }
  490. // TerraTech facility summary result type
  491. export interface TerraTechSummaryRow {
  492. wellName: string | null;
  493. corpId: string | null;
  494. facilityId: string | null;
  495. gas: number | null;
  496. oil: number | null;
  497. water: number | null;
  498. state: string | null;
  499. county: string | null;
  500. daysQ1: number | null;
  501. daysQ2: number | null;
  502. daysQ3: number | null;
  503. daysQ4: number | null;
  504. daysQ1Pct: number | null;
  505. daysQ2Pct: number | null;
  506. daysQ3Pct: number | null;
  507. daysQ4Pct: number | null;
  508. gasCorr: number | null;
  509. oilCorr: number | null;
  510. waterCorr: number | null;
  511. isMissingFacilityId: string | null;
  512. isMissingCounty: string | null;
  513. }
  514. // Get TerraTech facility summary for an import
  515. export async function getTerraTechFacilitySummary(importId: number) {
  516. try {
  517. if (!importId || isNaN(importId)) {
  518. return { success: false, error: 'Invalid import ID' };
  519. }
  520. // Check if import exists
  521. const importRecord = await prisma.import.findUnique({
  522. where: { id: importId },
  523. include: { layout: true }
  524. });
  525. if (!importRecord) {
  526. return { success: false, error: 'Import not found' };
  527. }
  528. // Execute the complex aggregation query using raw SQL
  529. const results = await prisma.$queryRaw<TerraTechSummaryRow[]>`
  530. SELECT
  531. s.well_name AS "wellName",
  532. s."corpId" AS "corpId",
  533. gfi.odeq_fac_id AS "facilityId",
  534. CAST(SUM(CAST(s.gas_production AS DECIMAL)) AS DECIMAL(10,2)) AS "gas",
  535. CAST(SUM(CAST(s.oil_production AS DECIMAL)) AS DECIMAL(10,2)) AS "oil",
  536. CAST(SUM(CAST(s.water_production AS DECIMAL)) AS DECIMAL(10,2)) AS "water",
  537. s.state AS "state",
  538. cr.county_parish AS "county",
  539. SUM(CASE WHEN q."Quarter" = 1 THEN q."DaysInMonth" ELSE 0 END) AS "daysQ1",
  540. SUM(CASE WHEN q."Quarter" = 2 THEN q."DaysInMonth" ELSE 0 END) AS "daysQ2",
  541. SUM(CASE WHEN q."Quarter" = 3 THEN q."DaysInMonth" ELSE 0 END) AS "daysQ3",
  542. SUM(CASE WHEN q."Quarter" = 4 THEN q."DaysInMonth" ELSE 0 END) AS "daysQ4",
  543. CAST(
  544. (SUM(CASE WHEN q."Quarter" = 1 THEN q."DaysInMonth" ELSE 0 END)::DECIMAL /
  545. CASE WHEN (EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 4 = 0
  546. AND (EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 100 != 0
  547. OR EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 400 = 0))
  548. THEN 366 ELSE 365 END * 100)
  549. AS DECIMAL(5,1)
  550. ) AS "daysQ1Pct",
  551. CAST(
  552. (SUM(CASE WHEN q."Quarter" = 2 THEN q."DaysInMonth" ELSE 0 END)::DECIMAL /
  553. CASE WHEN (EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 4 = 0
  554. AND (EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 100 != 0
  555. OR EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 400 = 0))
  556. THEN 366 ELSE 365 END * 100)
  557. AS DECIMAL(5,1)
  558. ) AS "daysQ2Pct",
  559. CAST(
  560. (SUM(CASE WHEN q."Quarter" = 3 THEN q."DaysInMonth" ELSE 0 END)::DECIMAL /
  561. CASE WHEN (EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 4 = 0
  562. AND (EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 100 != 0
  563. OR EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 400 = 0))
  564. THEN 366 ELSE 365 END * 100)
  565. AS DECIMAL(5,1)
  566. ) AS "daysQ3Pct",
  567. CAST(
  568. (SUM(CASE WHEN q."Quarter" = 4 THEN q."DaysInMonth" ELSE 0 END)::DECIMAL /
  569. CASE WHEN (EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 4 = 0
  570. AND (EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 100 != 0
  571. OR EXTRACT(YEAR FROM NOW() AT TIME ZONE 'America/Detroit')::INT % 400 = 0))
  572. THEN 366 ELSE 365 END * 100)
  573. AS DECIMAL(5,1)
  574. ) AS "daysQ4Pct",
  575. CAST(CASE WHEN SUM(CAST(s.gas_production AS DECIMAL)) < 0 THEN 0 ELSE SUM(CAST(s.gas_production AS DECIMAL)) END AS DECIMAL(10,2)) AS "gasCorr",
  576. CAST(CASE WHEN SUM(CAST(s.oil_production AS DECIMAL)) < 0 THEN 0 ELSE SUM(CAST(s.oil_production AS DECIMAL)) END AS DECIMAL(10,2)) AS "oilCorr",
  577. CAST(CASE WHEN SUM(CAST(s.water_production AS DECIMAL)) < 0 THEN 0 ELSE SUM(CAST(s.water_production AS DECIMAL)) END AS DECIMAL(10,2)) AS "waterCorr",
  578. CASE WHEN gfi.odeq_fac_id IS NULL THEN 'Yes' ELSE NULL END AS "isMissingFacilityId",
  579. CASE WHEN cr.county_parish IS NULL THEN 'Yes' ELSE NULL END AS "isMissingCounty"
  580. FROM gow_data s
  581. INNER JOIN LATERAL (
  582. SELECT
  583. DATE_PART('quarter', s.month::DATE) AS "Quarter",
  584. DATE_PART('days',
  585. DATE_TRUNC('month', s.month::DATE)
  586. + '1 MONTH'::INTERVAL
  587. - '1 DAY'::INTERVAL
  588. ) AS "DaysInMonth"
  589. ) q ON TRUE
  590. LEFT JOIN gow_fac_id gfi ON s."corpId" = gfi.corp_id AND gfi."importId" = s."importId"
  591. LEFT JOIN gow_corp_ref cr ON cr.corporate_id = s."corpId" AND cr."importId" = s."importId"
  592. WHERE s."importId" = ${importId}
  593. GROUP BY s.well_name, s."corpId", gfi.odeq_fac_id, s.state, cr.county_parish
  594. ORDER BY s.well_name
  595. `;
  596. // Serialize Decimal objects to plain numbers for client components
  597. const serializedRows = results.map(row => ({
  598. wellName: row.wellName,
  599. corpId: row.corpId,
  600. facilityId: row.facilityId,
  601. gas: row.gas !== null ? Number(row.gas) : null,
  602. oil: row.oil !== null ? Number(row.oil) : null,
  603. water: row.water !== null ? Number(row.water) : null,
  604. state: row.state,
  605. county: row.county,
  606. daysQ1: row.daysQ1 !== null ? Number(row.daysQ1) : null,
  607. daysQ2: row.daysQ2 !== null ? Number(row.daysQ2) : null,
  608. daysQ3: row.daysQ3 !== null ? Number(row.daysQ3) : null,
  609. daysQ4: row.daysQ4 !== null ? Number(row.daysQ4) : null,
  610. daysQ1Pct: row.daysQ1Pct !== null ? Number(row.daysQ1Pct) : null,
  611. daysQ2Pct: row.daysQ2Pct !== null ? Number(row.daysQ2Pct) : null,
  612. daysQ3Pct: row.daysQ3Pct !== null ? Number(row.daysQ3Pct) : null,
  613. daysQ4Pct: row.daysQ4Pct !== null ? Number(row.daysQ4Pct) : null,
  614. gasCorr: row.gasCorr !== null ? Number(row.gasCorr) : null,
  615. oilCorr: row.oilCorr !== null ? Number(row.oilCorr) : null,
  616. waterCorr: row.waterCorr !== null ? Number(row.waterCorr) : null,
  617. isMissingFacilityId: row.isMissingFacilityId,
  618. isMissingCounty: row.isMissingCounty,
  619. }));
  620. return {
  621. success: true,
  622. data: {
  623. importId,
  624. importName: importRecord.name,
  625. layoutName: importRecord.layout.name,
  626. rows: serializedRows
  627. }
  628. };
  629. } catch (error) {
  630. console.error('Error fetching TerraTech facility summary:', error);
  631. return { success: false, error: 'Failed to fetch facility summary' };
  632. }
  633. }