From 3c39a7a4b1c70cc543ba75b3b506c01e2d838e58 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 23 May 2024 18:08:15 +0800 Subject: [PATCH] update --- .../modules/report/service/ReportService.kt | 116 ++++++++++++++++++ .../modules/report/web/ReportController.kt | 24 ++++ .../modules/report/web/model/ReportRequest.kt | 5 + .../AR05_Project Completion Report.xlsx | Bin 12725 -> 12475 bytes ...h Outstanding Accounts Receivable v02.xlsx | Bin 12805 -> 12665 bytes 5 files changed, 145 insertions(+) diff --git a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt index 20f497a..42c9754 100644 --- a/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt +++ b/src/main/java/com/ffii/tsms/modules/report/service/ReportService.kt @@ -20,6 +20,7 @@ import org.apache.poi.ss.util.CellAddress import org.apache.poi.ss.util.CellRangeAddress import org.apache.poi.ss.util.CellUtil import org.apache.poi.xssf.usermodel.XSSFWorkbook +import org.hibernate.jdbc.Work import org.springframework.core.io.ClassPathResource import org.springframework.stereotype.Service import java.io.ByteArrayOutputStream @@ -50,6 +51,8 @@ open class ReportService( private val SALART_LIST_TEMPLATE = "templates/report/Salary Template.xlsx" private val LATE_START_REPORT = "templates/report/AR01_Late Start Report v01.xlsx" private val RESOURCE_OVERCONSUMPTION_REPORT = "templates/report/AR03_Resource Overconsumption.xlsx" + private val COMPLETE_PROJECT_OUTSTANDING_RECEIVABLE = "templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx" + private val COMPLETION_PROJECT = "templates/report/AR05_Project Completion Report.xlsx" // ==============================|| GENERATE REPORT ||============================== // @@ -174,6 +177,28 @@ open class ReportService( return outputStream.toByteArray() } + @Throws(IOException::class) + fun generateProjectCompletionReport( + args: MutableMap, + result: List> + ): ByteArray { + var REPORT_PATH: String = COMPLETE_PROJECT_OUTSTANDING_RECEIVABLE + if (args.get("outstanding") as Boolean == false) { + REPORT_PATH = COMPLETION_PROJECT + } + // Generate the Excel report with query results + val workbook: Workbook = createProjectCompletionReport( + args, + result, + REPORT_PATH + ) + // Write the workbook to a ByteArrayOutputStream + val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() + workbook.write(outputStream) + workbook.close() + + return outputStream.toByteArray() + } @Throws(IOException::class) fun exportSalaryList(salarys: List): ByteArray { @@ -1061,6 +1086,49 @@ open class ReportService( return workbook } + private fun createProjectCompletionReport( + args: MutableMap, + result: List>, + templatePath: String + ): Workbook { + val resource = ClassPathResource(templatePath) + val templateInputStream = resource.inputStream + val workbook: Workbook = XSSFWorkbook(templateInputStream) + + val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") + val monthStyle = workbook.createDataFormat().getFormat("MMM YYYY") + val dateStyle = workbook.createDataFormat().getFormat("dd/mm/yyyy") + + val sheet: Sheet = workbook.getSheetAt(0) + + var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field + var columnIndex = 2 + var tempRow = sheet.getRow(rowIndex) + var tempCell = tempRow.getCell(columnIndex) + tempCell.setCellValue(FORMATTED_TODAY) + + rowIndex = 2 + tempCell = sheet.getRow(rowIndex).getCell(columnIndex) + tempCell.setCellValue("${args.get("startDate").toString()} to ${args.get("endDate").toString()}") + + rowIndex = 5 + columnIndex = 0 + result.forEachIndexed { index, obj -> + tempCell = sheet.getRow(rowIndex).createCell(columnIndex) + tempCell.setCellValue((index + 1).toDouble()) + val keys = obj.keys.toList() + keys.forEachIndexed { keyIndex, key -> + tempCell = sheet.getRow(rowIndex).getCell(columnIndex + keyIndex + 1) ?: sheet.getRow(rowIndex).createCell(columnIndex + keyIndex + 1) + when (obj[key]) { + is Double -> tempCell.setCellValue(obj[key] as Double) + else -> tempCell.setCellValue(obj[key] as String ) + } + } + rowIndex++ + } + return workbook + } + private fun createProjectResourceOverconsumptionReport( team: String, @@ -1273,6 +1341,54 @@ open class ReportService( ) return jdbcDao.queryForList(sql.toString(), args) } + open fun getProjectCompletionReport(args: Map): List> { + val sql = StringBuilder("select" + + " result.code, " + + " result.name, " + + " result.teamCode, " + + " result.custCode, " ) + if (args.get("outstanding") as Boolean) { + sql.append(" result.totalBudget - COALESCE(i.issueAmount , 0) + COALESCE(i.issueAmount, 0) - COALESCE(i.paidAmount, 0) as `Receivable Remained`, ") + } + sql.append( + " DATE_FORMAT(result.actualEnd, '%d/%m/%Y') as actualEnd " + + " from ( " + + " SELECT " + + " pt.project_id, " + + " min(p.code) as code, " + + " min(p.name) as name, " + + " min(t.code) as teamCode, " + + " min(c.code) as custCode, " + + " min(p.actualEnd) as actualEnd, " + + " sum(COALESCE(tns.totalConsumed*sal.hourlyRate, 0)) as totalBudget " + + " FROM ( " + + " SELECT " + + " t.staffId, " + + " sum(t.normalConsumed + COALESCE(t.otConsumed, 0)) as totalConsumed, " + + " t.projectTaskId AS taskId " + + " FROM timesheet t " + + " LEFT JOIN staff s ON t.staffId = s.id " + + " LEFT JOIN team te on s.teamId = te.id " + + " GROUP BY t.staffId, t.projectTaskId " + + " order by t.staffId " + + " ) AS tns " + + " inner join project_task pt ON tns.taskId = pt.id " + + " left JOIN staff s ON tns.staffId = s.id " + + " left join salary sal on s.salaryId = sal.salaryPoint " + + " left JOIN team t ON s.teamId = t.id " + + " left join project p on p.id = pt.project_id " + + " left join customer c on c.id = p.customerId " + + " where p.deleted = false " + + " and p.status = 'Completed' " + + " and p.actualEnd BETWEEN :startDate and :endDate " + + " group by pt.project_id " + + " ) as result " + + " left join invoice i on result.code = i.projectCode " + + " order by result.actualEnd " + ) + + return jdbcDao.queryForList(sql.toString(), args) + } open fun getProjectResourceOverconsumptionReport(args: Map): List> { val sql = StringBuilder("WITH teamNormalConsumed AS (" diff --git a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt index 8c8e045..b6be334 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/ReportController.kt @@ -141,6 +141,30 @@ class ReportController( .body(ByteArrayResource(reportResult)) } + @PostMapping("/ProjectCompletionReportwithOutstandingAccountsReceivable") + @Throws(ServletRequestBindingException::class, IOException::class) + fun ProjectCompletionReport(@RequestBody @Valid request: ProjectCompletionReport): ResponseEntity { + val args: MutableMap = mutableMapOf( + "startDate" to request.startDate, + "endDate" to request.endDate, + "outstanding" to request.outstanding + ) + val result = excelReportService.getProjectCompletionReport(args); + val reportResult: ByteArray = excelReportService.generateProjectCompletionReport(args, result) + // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") + if (request.outstanding) { + return ResponseEntity.ok() + // .contentType(mediaType) + .header("filename", "Project Completion Report with Outstanding Accounts Receivable - " + " - " + LocalDate.now() + ".xlsx") + .body(ByteArrayResource(reportResult)) + } + return ResponseEntity.ok() +// .contentType(mediaType) + .header("filename", "Project Completion Report - " + " - " + LocalDate.now() + ".xlsx") + .body(ByteArrayResource(reportResult)) + + } + @GetMapping("/test/{id}") fun test(@PathVariable id: Long): List { val project = projectRepository.findById(id).orElseThrow() diff --git a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt index b5797e3..70671a7 100644 --- a/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt +++ b/src/main/java/com/ffii/tsms/modules/report/web/model/ReportRequest.kt @@ -36,4 +36,9 @@ data class ProjectResourceOverconsumptionReport ( val custId: Long?, val status: String, val lowerLimit: Double +) +data class ProjectCompletionReport ( + val startDate: LocalDate, + val endDate: LocalDate, + val outstanding: Boolean ) \ No newline at end of file diff --git a/src/main/resources/templates/report/AR05_Project Completion Report.xlsx b/src/main/resources/templates/report/AR05_Project Completion Report.xlsx index d5bd3cc109e1c637b807a2dae35032167abc93ff..126f10c084b8f3e52d2ffb21730aef5fef89e34f 100644 GIT binary patch delta 4130 zcmZ8kcQ72@8eO~U>LONIy{%pr5z$+S-U(4duvkG5!HP~qTOwNY-bwVR5hc<4iruIo zB#Xo*T0HOfW?tUBZ|>ZC=YHqEd*+<)ym9GrYQ&JDa&KV5X+Qt~fD8bjy*h%uB!ZBB z2xlY`As+1QRcqmGv?59ON7>_&`{)V%_3fHIUC@XaeV5KTm^v2glbK2>mSbw!3L>)cefHC!-gCAjb6wV#KW?R*5F?I=UaC}I zK4Z!VE{mT;3R8!g6|Ovy`$>o>pbuvm;zsQosn7PClra=O$6|P+X%uG2{=iu!2|UDS zgUsDDLSR*_P}ZQJrR3>t%M!ubX*hEUQriVqXDK2@iszh;HTtCP7t(AIb3UWHxbf&B zAxqqh_cp*}_m$Gdgi>hgz~(zP9WlRp6qq^x2l5%MHh|v-wioswJ}*syUak z{i8#LP2WnnWY(9S+Hy@kF}xTqnHaPEe4I1pbKx}xrv+MlNGQ&h`8ivr(rj>HN(z2k zB^FDVy}5rE3*isiwvqPP`_@}6Jlhrh`y<80pRK?}*52xm+dG?l;6uSxR2Qx3>gLGH zChkWhz#^9ysgNWl_LA$$J#6-p;P8?;Ijhoz3Hy1bcR$l<-pcOaO|6m1`Sq#ww$8Re z`Ab^twk+?y8_Q2(oXJC={gh3llLp=)+H29&cFddDaGBP8lPd5@bG+ej0K-YZP3F_- zL%yFWC2Bj9C0gy!L=2OmhHjtMmm@NJ};-#`R%uk*L*I>(iSpBr~ms(OnOV$D9TEeMJ;KeebSk;(}n@!Cc#}wICZE-OaU6j$OjS$jnXQJE(3jpW7crM46CFYTB$6My~AhI`3g^hWbBouVi^q7%p&r7)ep!b*-i(k zyg(>~vKGSdMw4)8>qz9ps@uu>P`m8!ndX8HG}LH#NUZv{=8^(Jn2|8 zDh}>n;FNTT#jMi5YWFaSR(nOHxf{jey@Qfj78d(0)T^{aMAXGZM2IE_-4LDe2bOlI zMr_*xmtN6zJx&{*&L&r_t(>8ByVD*2%t*db(C#xTx~q`fvn{cYKn}wPK(ksZDvTS$ z+zI^;?QHZGehEH5Y73blbEI=69Si)WawX4~m;Y=TFHC%*B&lK>BK;z!8QoDHBswMB zzf`HJj8@I7eY!3f@<>>b3K zB&8Jb@A>4$uuqKMLc+XT8Cg&>vH;na4iS%lXB#p5&sM5W2*jgg1EH5tJ1oFg(!^jCJlKN4Bn@L^6Sd3{w58L@{O0QF(TWfb= zK>Lu>Nd2}*RUEMTs7#@MVX)j94}NLBwTZLOta_mS{Mf|qLuuUmL15k=fuE|~uBY0g zX&#FDMT03^_e%Fkz3SXf3Cd)GO?wW7dD>wfr;wVXEnTHpQrA)zYslU;j#0rmgrATY zUxrz?&Bd6PiOS=Ylu%z$dfg}t7p+F2z~Xir$)5Y**3>7?bElhMI816(0RN~c1;aa1 z6iiCZQJfe60MP>g)c+Yi`~x0)x%&Tm^mu1Ip1dMQ*8|zPMc#=xcF{$!rANV9BLE)8 z0d1-cuQiu7VKaq0&+~Xxla-Fm8N=cTsT#Clg*@K4VvRe^Z`qzK66({Q4dJqpZpt)q z$*<=5Wqlee$saWB+RK+ewR!K48gOemxp?GxY*3OOzZLHn>*60H@?&{tuc7!(J*+3k zMUHRHRw|T67lHvi5;x185+mT6d4rRr_1)0Nit0Xu6~TwJW465IjWr zn8?rDo{?{`V32(Y#OBJT%XK27>*II}E4G4oSj>lG`W;(&kX=&wS>ICoeU|?FZGM1- zICGEFxtQKOqh|X(*~IFg_teFb>p2A2nEcj2DxJbX9*0xqyYWP$-thA;lPLw^XD7Wl zlnH{;oT1ZmTT!-911m0$q99hK#(?!f9(vjKmoV35Xad-JF#;cBMEyA@qg~p5qwRfA zx8{C*Kjuc8SFSK^z(nX&PstFccnC5zha7*f5`OEoE&kc;XdqR_ZCYL{BlwSzU_`Dk z7e`S*7?Yyb6J}C?_jNoz8(m_(b)l=G zgZ?0SfJ5zJ9*A9!*gh_AemGZaj?}vMqqYdt2!*)oZXJh6%iZ{f^fi3JAK_^grsvZ> z$K&ij8(x;EXq7eH{-QxR7~H>T4F-M|9NltM)K#j%^M!rDul2DK1C$RO_%#AY2VTU}t}!da!R0y5 zIw#NnkiJ_VxsBH29=jHCV3jAk~i{H~bO?R1qsb8n$6<(}w9kg3h>UdQ(-QyA+ zf(SIpWFcLULae-5%SN)kNNmJA|2WPRmtk-N)%#pcva`Y;Oa< zO45Dl>G-DlJ*>}@G0AYkUC!blEH3X$6xoMLel{`G6h#nCLd932x&qHGW^AKdaBKMX z(Qx(D$EEo>oz>CIFqtoNv;vl zkcdg;y+rKM8x@ULP5Km;mSEEW(kQ0oi+clzF%fEs&T6$4VA2;su2pi_g<=CAw)6q2Cd;DV_U(NEet|WHV`; zT0E+g>9q`sV<+9jjEP{^b>m*Zuvh2YvY<(c@<)+}5e^7xecvB~c&W+nMN&Y_#xv%!X=UYgy&=7}?c z-hJGEtgKGN5(NI6qtVn720$_}+D<|g`Zp#30L)jk{96iCXeBCEbdQ8EQ7(E{LV)f6 zz3?y80WB=a1^vrx0D$pt^Xk?PMY~C=0=L=GryTs~aYt@?Vf{mzC}Y=@3D>J62FYx?=$eY4`%t61qzxjj)u|f^>Js3&{PdC8;cUYCNp=XXTMOZ%S&icDv4-Pm|t0Jnz?8B=BG%7USNNOENC_KwO+sOD1 zGf4q24g88`t*s-M1N<+qL&EAN#LiWs?b``RLmcVjs6_Nl>WGrsmOgx?*WiP4aBSg{ zCIL=-NpE^CJ@@DZ7l8=N zUp}O+1u|qGpW#y5zl&IY@vX2ryp5Q=sSxl&r*-?*8%^5wa9UM}g*Va7a^Ko5_g?Yf zEYHz|kc>3qgxX5S20tspXg*t!eRc_oIT9+idHbw74HS8)DegHnA%eH3oj!w6YmH0Y z5rQA;IN*)O86l#0v!m3=V?vb4Dc94GqlN>bwbC8QTl5RoLVM-mSleNlynIAO&Okpg zE_B)OuA<8I8_y1lt*`Q(k&eOVCoHTl@y)>a_N-+pACO>EXO*7cdXcebMCj|x(Zh6rwm0skp|VTBIZS6i8!%4kj+{zbLXhIxlCmqX!yVK zZq;MZ7>&0C=E|nk_}5Go7A!n!A=Td9T{51nZ;>U7&=_B!vm8iEAK$d*ynXgWuc#@|q)UhBLCI7v2B4 zq^!wuy?t0=oGn0K6|DYr@OAE>e&m`>{$PIBC+cg6 zAqw(NXE4L@2K&c%+Kx}VxIYCwI{Hj>+%4z(Ng5g5@%&Rjc$-$ylDKq`F*bFt1naax z#&)(ImG8EB`w%(Si?dCsT38@H=}Tf{Jbe8V^ha_7US|ss?S9HXSh=1aQvO#l@m)f4 z-P}%}Ion`$KqS5Mbf{vT&2GljQCHjR_Q2Vp;rW8#)ZD&g_LwZglbZ z{bpAK@moA-^K!iQh~KJ*s>33uqB>nl?0!7gkFJkw<>s+6cjtC1o=!%JmbfF>+m3gRG=F|;={B=#l$a(URtI43Wc-pKb zxTu7db83rWy4lhoj+AyyJ%2gp1)B7gWAHFPdu%f0Sg+Sb9rN$6CCmg@-HE;M=%;`g zbmw!96xRdqZ>&R!Mp9IT70t2Y-?M0=If>n~F@Ak38@fNA3_R`qU~5M;2~~n%6Zudr z_}A#7j3O&czqD0gMmxDAn#_G!1ojG?`}?D#ms}i0X@D3d@Kecmv3Z#SyU0aT617>w zye&;p8E%Q7)#{{pM=2i18?13NlJ~%zf{u)%xj4R>fnouFBJ`)O4u4qTRKg4|oaYe$!JUCL&aAM#7LD{kM&kG+;h}sk*Oq z6xP&2I;ZT0@+O|<&^Dq-6@ctJQ0Gt_yGJD0EsnLJ>|`lUAqvlZZsioE)Q4t9Nl+Mj zrGT0T`;B%%xsh~3ICWbM;_v-nX){ zrQt2Qhk`nPXi1}jqnE~(&L;V+o=BW@AL(gWr0(u--?yK_a}07e_BH)R%RzcwM;K(u z`W9M~MQb5Wv2sY!Vu*R~JriZ?PH z_aJaK{oC(s?3riVs&H zMBzP1L;3xFJm53?LlPkz5a>M^1S0)6&GGRKggN;9O>)Lfv^}>TQFK!80~Ef`&udFQ z$CM24HHG6gy(!X+uyl#9kB62h&Xl(gUJ;#3#`uh#pMQvJD?p58= zuI(H4$#F@2QrdPTQN@a_A$U@0rL)$Tv?0|g3Ybv7In3#&IWXhBKSr@b68noT2oUjTVL+$1K5@5u?PN{Uoc~<`9iu==g*;ukfhWfRVvm9se3X zg-sB1pfBPI;kF<5!-VdOL;C*{;-<mvIg^ku7haME!bH$<4izBCBDf>YR$zW|Y zEbES{;%TF5Ui$hScSf>bvE#wB6%jJ0fPB+2J#-x!{JLwda}(G0(!E;Nd#EZ)w9{v! zxRg=g_T>z4ce`vQe|L2vB!AT3KIVxzLazKWh)b?5UVti5QD{ki-9w(=Q-UbuYVn(3 z6IX|JnhLRA)1Xqh;txVozU2|4niy+_tQUxitp`={1gf+{KD4l^VhyxJof3)yR!CqZ zH}}AiOPpOy`ttDBnOk#tdF$(4xQ5?#-KW1 z8{t27!|;JLH<&R;bOWD*Eyag!a_~!epM2!jg}1fUQ#qPVQ_i5KpX@=OM-0#Nru-|JK3l6CCS~}?j7`Xt zl_RW>ch^H0AndAf>=6M5ri$c%i4`0c-SL&e2oZo9?6Q+`~X^!gqBhc=gb?JsL}mOVZSLIOPbHy-N}9@@^UBK4zL zdVXNBj|+l#f12_BBL5p#h@6$$T@Y9xP$~{$nt={TOzqL@6DEl{(P&qOOuqfNOq7FW zjkzHoc_~66E#Vs-hRJ2e%ZTNk8Qxg!?(rUFWc*AiU8C12*rEEJ}f_cZ0nQH z@Ke3|KyYQ>wr*e4cA+VJVWnmB*sN*wY}oWnL$H;g<&pb5kjevC`TqlIjwQ)qnXcsJ5{^AEx2ASv|Jhp73QEA z!jzqjO)vkH-^{!*p4I<1_MbCK6A^+Uvh)o8B+9x$l7|fouyA`V42e0x$B@H=by1BE z6$N0$k}qs(lS98mZ==$(XyCq0cVUJvoR(;|2irZF@)_U)Wc5Uy-Jk29NhPpHVjHwm zeL(N-J9Loj@zf^s_EeZDCMR3Z6g78_HkYi{&MnUM$+m@ElW{b> z^WluL**<&T-$o6V5JMbBYGUsu099k>fFq4`z3P*a*M4@-3~^qMQd z^PdnS{DsLHdiXT%{TWUFZOu$$u`vAiuYo|2{|f)SXc-YOAqDJJCd5Y}ee88+#GQ~7!+$dVXR&erf7EGs V5k|tY*!d3;2w^t7K*7I-{{n9XBdY)a diff --git a/src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx b/src/main/resources/templates/report/AR06_Project Completion Report with Outstanding Accounts Receivable v02.xlsx index 25a4a74b93997993978213a8e1fe4fc3ad13abd2..db5ea39aafdf68860d7c3975b35b780ed2f3f64a 100644 GIT binary patch delta 4313 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 ruecFdkOSQ+F3