| @@ -1,97 +0,0 @@ | |||||
| package com.ffii.tsms.modules.common.service | |||||
| import com.ffii.tsms.modules.project.entity.Project | |||||
| import org.apache.poi.ss.usermodel.Cell | |||||
| import org.apache.poi.ss.usermodel.CellStyle | |||||
| import org.apache.poi.ss.usermodel.HorizontalAlignment | |||||
| import org.apache.poi.ss.usermodel.Row | |||||
| import org.apache.poi.ss.usermodel.Sheet | |||||
| import org.apache.poi.ss.usermodel.Workbook | |||||
| import org.apache.poi.ss.util.CellRangeAddress | |||||
| import org.apache.poi.ss.util.CellUtil | |||||
| import org.apache.poi.xssf.usermodel.XSSFWorkbook | |||||
| import org.springframework.core.io.ClassPathResource | |||||
| import org.springframework.stereotype.Service | |||||
| import java.io.ByteArrayOutputStream | |||||
| import java.io.IOException | |||||
| import java.time.LocalDate | |||||
| import java.time.LocalDateTime | |||||
| import java.time.format.DateTimeFormatter | |||||
| @Service | |||||
| open class ExcelReportService { | |||||
| private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") | |||||
| private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER) | |||||
| private val EX02_PROJECT_CASH_FLOW_REPORT = "templates/report/EX02_Project Cash Flow Report.xlsx" | |||||
| // ==============================|| GENERATE REPORT ||============================== // | |||||
| @Throws(IOException::class) | |||||
| fun generateEX02ProjectCashFlowReport(project: Project): ByteArray { | |||||
| // Generate the Excel report with query results | |||||
| val workbook: Workbook = createEX02ProjectCashFlowReport(project, EX02_PROJECT_CASH_FLOW_REPORT) | |||||
| // Write the workbook to a ByteArrayOutputStream | |||||
| val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | |||||
| workbook.write(outputStream) | |||||
| workbook.close() | |||||
| return outputStream.toByteArray() | |||||
| } | |||||
| // ==============================|| CREATE REPORT ||============================== // | |||||
| @Throws(IOException::class) | |||||
| private fun createEX02ProjectCashFlowReport( | |||||
| project: Project, | |||||
| templatePath: String, | |||||
| ): Workbook { | |||||
| // please create a new function for each report template | |||||
| val resource = ClassPathResource(templatePath) | |||||
| val templateInputStream = resource.inputStream | |||||
| val workbook: Workbook = XSSFWorkbook(templateInputStream) | |||||
| val sheet: Sheet = workbook.getSheetAt(0) | |||||
| // val alignLeftStyle: CellStyle = workbook.createCellStyle().apply { | |||||
| // alignment = HorizontalAlignment.LEFT // Set the alignment to left | |||||
| // } | |||||
| // | |||||
| // val alignRightStyle: CellStyle = workbook.createCellStyle().apply { | |||||
| // alignment = HorizontalAlignment.RIGHT // Set the alignment to right | |||||
| // } | |||||
| var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field | |||||
| var columnIndex = 2 | |||||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | |||||
| setCellValue(FORMATTED_TODAY) | |||||
| } | |||||
| rowIndex = 2 | |||||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | |||||
| setCellValue(project.code) | |||||
| } | |||||
| rowIndex = 3 | |||||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | |||||
| setCellValue(project.name) | |||||
| } | |||||
| rowIndex = 4 | |||||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | |||||
| setCellValue(if (project.customer?.name == null) "N/A" else project.customer?.name) | |||||
| } | |||||
| rowIndex = 5 | |||||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | |||||
| setCellValue(if (project.teamLead?.team?.name == null) "N/A" else project.teamLead?.team?.name) | |||||
| } | |||||
| rowIndex = 9 | |||||
| sheet.getRow(rowIndex).apply { | |||||
| getCell(1).setCellValue(project.expectedTotalFee!! * 0.8) | |||||
| getCell(2).setCellValue(project.expectedTotalFee!!) | |||||
| } | |||||
| return workbook | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,43 @@ | |||||
| package com.ffii.tsms.modules.project.entity | |||||
| import com.ffii.core.entity.BaseEntity | |||||
| import com.ffii.tsms.modules.data.entity.Customer | |||||
| import jakarta.persistence.Column | |||||
| import jakarta.persistence.Entity | |||||
| import jakarta.persistence.JoinColumn | |||||
| import jakarta.persistence.ManyToOne | |||||
| import jakarta.persistence.Table | |||||
| import jakarta.validation.constraints.NotNull | |||||
| import java.time.LocalDate | |||||
| @Entity | |||||
| @Table(name = "invoice") | |||||
| open class Invoice : BaseEntity<Long>(){ | |||||
| @NotNull | |||||
| @Column(name = "invoiceNo", length = 45) | |||||
| open var invoiceNo: String? = null | |||||
| @NotNull | |||||
| @ManyToOne | |||||
| @JoinColumn(name = "milestonePaymentId") | |||||
| open var milestonePayment: MilestonePayment? = null | |||||
| @NotNull | |||||
| @Column(name = "invoiceDate") | |||||
| open var invoiceDate: LocalDate? = null | |||||
| @NotNull | |||||
| @Column(name = "dueDate") | |||||
| open var dueDate: LocalDate? = null | |||||
| @Column(name = "receiptDate") | |||||
| open var receiptDate: LocalDate? = null | |||||
| @NotNull | |||||
| @Column(name = "unpaidAmount") | |||||
| open var unpaidAmount: Double? = null | |||||
| @Column(name = "paidAmount") | |||||
| open var paidAmount: Double? = null | |||||
| } | |||||
| @@ -0,0 +1,9 @@ | |||||
| package com.ffii.tsms.modules.project.entity | |||||
| import com.ffii.core.support.AbstractRepository | |||||
| interface InvoiceRepository : AbstractRepository<Invoice, Long> { | |||||
| fun findAllByPaidAmountIsNotNullAndMilestonePaymentIn(milestonePayment: List<MilestonePayment>): List<Invoice> | |||||
| } | |||||
| @@ -1,5 +1,6 @@ | |||||
| package com.ffii.tsms.modules.project.entity | package com.ffii.tsms.modules.project.entity | ||||
| import com.fasterxml.jackson.annotation.JsonManagedReference | |||||
| import com.ffii.core.entity.IdEntity | import com.ffii.core.entity.IdEntity | ||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| @@ -15,6 +16,7 @@ open class Milestone : IdEntity<Long>() { | |||||
| @Column(name = "description") | @Column(name = "description") | ||||
| open var description: String? = null | open var description: String? = null | ||||
| @JsonManagedReference | |||||
| @OneToMany(mappedBy = "milestone", cascade = [CascadeType.ALL], orphanRemoval = true) | @OneToMany(mappedBy = "milestone", cascade = [CascadeType.ALL], orphanRemoval = true) | ||||
| open var milestonePayments: MutableList<MilestonePayment> = mutableListOf() | open var milestonePayments: MutableList<MilestonePayment> = mutableListOf() | ||||
| @@ -1,5 +1,6 @@ | |||||
| package com.ffii.tsms.modules.project.entity | package com.ffii.tsms.modules.project.entity | ||||
| import com.fasterxml.jackson.annotation.JsonBackReference | |||||
| import com.ffii.core.entity.IdEntity | import com.ffii.core.entity.IdEntity | ||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| import jakarta.validation.constraints.NotNull | import jakarta.validation.constraints.NotNull | ||||
| @@ -21,6 +22,7 @@ open class MilestonePayment : IdEntity<Long>() { | |||||
| open var description: String? = null | open var description: String? = null | ||||
| @ManyToOne | @ManyToOne | ||||
| @JsonBackReference | |||||
| @JoinColumn(name = "milestoneId") | @JoinColumn(name = "milestoneId") | ||||
| open var milestone: Milestone? = null | open var milestone: Milestone? = null | ||||
| } | } | ||||
| @@ -3,4 +3,5 @@ package com.ffii.tsms.modules.project.entity; | |||||
| import com.ffii.core.support.AbstractRepository | import com.ffii.core.support.AbstractRepository | ||||
| interface MilestonePaymentRepository : AbstractRepository<MilestonePayment, Long> { | interface MilestonePaymentRepository : AbstractRepository<MilestonePayment, Long> { | ||||
| fun findAllByMilestoneIn(milestones: List<Milestone>): List<MilestonePayment> | |||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| package com.ffii.tsms.modules.project.entity | package com.ffii.tsms.modules.project.entity | ||||
| import com.fasterxml.jackson.annotation.JsonManagedReference | |||||
| import com.ffii.core.entity.BaseEntity | import com.ffii.core.entity.BaseEntity | ||||
| import com.ffii.tsms.modules.data.entity.* | import com.ffii.tsms.modules.data.entity.* | ||||
| import jakarta.persistence.* | import jakarta.persistence.* | ||||
| @@ -4,9 +4,8 @@ import com.ffii.core.support.AbstractBaseEntityService | |||||
| import com.ffii.core.support.AbstractIdEntityService | import com.ffii.core.support.AbstractIdEntityService | ||||
| import com.ffii.core.support.JdbcDao | import com.ffii.core.support.JdbcDao | ||||
| import com.ffii.core.utils.PdfUtils | import com.ffii.core.utils.PdfUtils | ||||
| import com.ffii.tsms.modules.project.entity.* | |||||
| import com.ffii.tsms.modules.project.entity.MilestonePayment | |||||
| import com.ffii.tsms.modules.project.entity.MilestonePaymentRepository | |||||
| import com.ffii.tsms.modules.project.entity.projections.InvoicePDFReq | import com.ffii.tsms.modules.project.entity.projections.InvoicePDFReq | ||||
| import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo | import com.ffii.tsms.modules.project.entity.projections.InvoiceSearchInfo | ||||
| import net.sf.jasperreports.engine.JasperCompileManager | import net.sf.jasperreports.engine.JasperCompileManager | ||||
| @@ -21,9 +20,18 @@ import java.time.format.DateTimeFormatter | |||||
| @Service | @Service | ||||
| open class InvoiceService( | open class InvoiceService( | ||||
| private val invoiceRepository: InvoiceRepository, | |||||
| private val milestoneRepository: MilestoneRepository, | |||||
| private val milestonePaymentRepository: MilestonePaymentRepository, | private val milestonePaymentRepository: MilestonePaymentRepository, | ||||
| private val jdbcDao: JdbcDao, | private val jdbcDao: JdbcDao, | ||||
| ) : AbstractIdEntityService<MilestonePayment, Long, MilestonePaymentRepository>(jdbcDao, milestonePaymentRepository){ | |||||
| ) : AbstractIdEntityService<Invoice, Long, InvoiceRepository>(jdbcDao, invoiceRepository){ | |||||
| open fun findAllByProjectAndPaidAmountIsNotNull(project: Project): List<Invoice> { | |||||
| val milestones = milestoneRepository.findAllByProject(project) | |||||
| val milestonePayments = milestonePaymentRepository.findAllByMilestoneIn(milestones) | |||||
| return invoiceRepository.findAllByPaidAmountIsNotNullAndMilestonePaymentIn(milestonePayments) | |||||
| } | |||||
| open fun allMilestonePayments(): List<Map<String, Any>> { | open fun allMilestonePayments(): List<Map<String, Any>> { | ||||
| val sql = StringBuilder(" select " | val sql = StringBuilder(" select " | ||||
| + " mp.id, " | + " mp.id, " | ||||
| @@ -0,0 +1,160 @@ | |||||
| package com.ffii.tsms.modules.report.service | |||||
| import com.ffii.tsms.modules.project.entity.Invoice | |||||
| import com.ffii.tsms.modules.project.entity.Project | |||||
| import org.apache.poi.ss.usermodel.Sheet | |||||
| import org.apache.poi.ss.usermodel.Workbook | |||||
| import org.apache.poi.xssf.usermodel.XSSFDataFormat | |||||
| import org.apache.poi.xssf.usermodel.XSSFWorkbook | |||||
| import org.springframework.core.io.ClassPathResource | |||||
| import org.springframework.stereotype.Service | |||||
| import java.io.ByteArrayOutputStream | |||||
| import java.io.IOException | |||||
| import java.time.LocalDate | |||||
| import java.time.format.DateTimeFormatter | |||||
| @Service | |||||
| open class ReportService { | |||||
| private val DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd") | |||||
| private val FORMATTED_TODAY = LocalDate.now().format(DATE_FORMATTER) | |||||
| private val PROJECT_CASH_FLOW_REPORT = "templates/report/EX02_Project Cash Flow Report.xlsx" | |||||
| // ==============================|| GENERATE REPORT ||============================== // | |||||
| @Throws(IOException::class) | |||||
| fun generateProjectCashFlowReport(project: Project, invoices: List<Invoice>): ByteArray { | |||||
| // Generate the Excel report with query results | |||||
| val workbook: Workbook = createProjectCashFlowReport(project, invoices, PROJECT_CASH_FLOW_REPORT) | |||||
| // Write the workbook to a ByteArrayOutputStream | |||||
| val outputStream: ByteArrayOutputStream = ByteArrayOutputStream() | |||||
| workbook.write(outputStream) | |||||
| workbook.close() | |||||
| return outputStream.toByteArray() | |||||
| } | |||||
| // ==============================|| CREATE REPORT ||============================== // | |||||
| @Throws(IOException::class) | |||||
| private fun createProjectCashFlowReport( | |||||
| project: Project, | |||||
| invoices: List<Invoice>, | |||||
| templatePath: String, | |||||
| ): Workbook { | |||||
| // please create a new function for each report template | |||||
| val resource = ClassPathResource(templatePath) | |||||
| val templateInputStream = resource.inputStream | |||||
| val workbook: Workbook = XSSFWorkbook(templateInputStream) | |||||
| val sheet: Sheet = workbook.getSheetAt(0) | |||||
| // accounting style + comma style | |||||
| val accountingStyle = workbook.createDataFormat().getFormat("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)") | |||||
| var rowIndex = 1 // Assuming the location is in (1,2), which is the report date field | |||||
| var columnIndex = 2 | |||||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | |||||
| setCellValue(FORMATTED_TODAY) | |||||
| } | |||||
| rowIndex = 2 | |||||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | |||||
| setCellValue(project.code) | |||||
| } | |||||
| rowIndex = 3 | |||||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | |||||
| setCellValue(project.name) | |||||
| } | |||||
| rowIndex = 4 | |||||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | |||||
| setCellValue(if (project.customer?.name == null) "N/A" else project.customer?.name) | |||||
| } | |||||
| rowIndex = 5 | |||||
| sheet.getRow(rowIndex).getCell(columnIndex).apply { | |||||
| setCellValue(if (project.teamLead?.team?.name == null) "N/A" else project.teamLead?.team?.name) | |||||
| } | |||||
| rowIndex = 9 | |||||
| sheet.getRow(rowIndex).apply { | |||||
| getCell(1).apply { | |||||
| setCellValue(project.expectedTotalFee!!) | |||||
| cellStyle.dataFormat = accountingStyle | |||||
| } | |||||
| getCell(2).apply { | |||||
| setCellValue(project.expectedTotalFee!! / 0.8) | |||||
| cellStyle.dataFormat = accountingStyle | |||||
| } | |||||
| } | |||||
| rowIndex = 10 | |||||
| val actualIncome = invoices.sumOf { invoice -> invoice.paidAmount!! } | |||||
| sheet.getRow(rowIndex).apply { | |||||
| getCell(1).apply { | |||||
| // TODO: Replace by actual expenditure | |||||
| setCellValue(actualIncome * 0.8) | |||||
| cellStyle.dataFormat = accountingStyle | |||||
| } | |||||
| getCell(2).apply { | |||||
| setCellValue(actualIncome) | |||||
| cellStyle.dataFormat = accountingStyle | |||||
| } | |||||
| } | |||||
| rowIndex = 11 | |||||
| sheet.getRow(rowIndex).apply { | |||||
| getCell(1).apply { | |||||
| // TODO: Replace by actual expenditure | |||||
| cellFormula = "B10-B11" | |||||
| cellStyle.dataFormat = accountingStyle | |||||
| } | |||||
| getCell(2).apply { | |||||
| cellFormula = "C10-C11" | |||||
| cellStyle.dataFormat = accountingStyle | |||||
| } | |||||
| } | |||||
| // TODO: Add expenditure | |||||
| // formula =IF(B17>0,D16-B17,D16+C17) | |||||
| rowIndex = 15 | |||||
| val dateFormatter = DateTimeFormatter.ofPattern("MMM YYYY") | |||||
| invoices.forEach { invoice: Invoice -> | |||||
| sheet.getRow(rowIndex++).apply { | |||||
| getCell(0).apply { | |||||
| setCellValue(invoice.receiptDate!!.format(dateFormatter)) | |||||
| } | |||||
| getCell(1).apply { | |||||
| setCellValue(0.0) | |||||
| cellStyle.dataFormat = accountingStyle | |||||
| } | |||||
| getCell(2).apply { | |||||
| setCellValue(invoice.paidAmount!!) | |||||
| cellStyle.dataFormat = accountingStyle | |||||
| } | |||||
| getCell(3).apply { | |||||
| val lastRow = rowIndex - 1 | |||||
| if (lastRow == 15) { | |||||
| cellFormula = "C{currentRow}".replace("{currentRow}", rowIndex.toString()) | |||||
| } else { | |||||
| cellFormula = "IF(B{currentRow}>0,D{lastRow}-B{currentRow},D{lastRow}+C{currentRow})".replace("{currentRow}", rowIndex.toString()).replace("{lastRow}", lastRow.toString()) | |||||
| } | |||||
| cellStyle.dataFormat = accountingStyle | |||||
| } | |||||
| getCell(4).apply { | |||||
| setCellValue(invoice.milestonePayment!!.description!!) | |||||
| } | |||||
| } | |||||
| } | |||||
| return workbook | |||||
| } | |||||
| } | |||||
| @@ -1,36 +1,46 @@ | |||||
| package com.ffii.tsms.modules.report.web | package com.ffii.tsms.modules.report.web | ||||
| import com.ffii.tsms.modules.common.service.ExcelReportService | |||||
| import com.ffii.tsms.modules.project.entity.ProjectRepository | |||||
| import com.ffii.tsms.modules.report.web.model.EX02ProjectCashFlowReportRequest | |||||
| import com.ffii.tsms.modules.project.entity.* | |||||
| import com.ffii.tsms.modules.report.service.ReportService | |||||
| import com.ffii.tsms.modules.project.service.InvoiceService | |||||
| import com.ffii.tsms.modules.report.web.model.ProjectCashFlowReportRequest | |||||
| import jakarta.validation.Valid | import jakarta.validation.Valid | ||||
| import org.springframework.core.io.ByteArrayResource | import org.springframework.core.io.ByteArrayResource | ||||
| import org.springframework.core.io.Resource | import org.springframework.core.io.Resource | ||||
| import org.springframework.http.MediaType | |||||
| import org.springframework.http.ResponseEntity | import org.springframework.http.ResponseEntity | ||||
| import org.springframework.web.bind.ServletRequestBindingException | import org.springframework.web.bind.ServletRequestBindingException | ||||
| import org.springframework.web.bind.annotation.GetMapping | |||||
| import org.springframework.web.bind.annotation.PathVariable | |||||
| import org.springframework.web.bind.annotation.PostMapping | import org.springframework.web.bind.annotation.PostMapping | ||||
| import org.springframework.web.bind.annotation.RequestBody | import org.springframework.web.bind.annotation.RequestBody | ||||
| import org.springframework.web.bind.annotation.RequestMapping | import org.springframework.web.bind.annotation.RequestMapping | ||||
| import org.springframework.web.bind.annotation.RequestParam | |||||
| import org.springframework.web.bind.annotation.RestController | import org.springframework.web.bind.annotation.RestController | ||||
| import java.io.IOException | import java.io.IOException | ||||
| import java.time.LocalDate | import java.time.LocalDate | ||||
| @RestController | @RestController | ||||
| @RequestMapping("/reports") | @RequestMapping("/reports") | ||||
| class ReportController(private val excelReportService: ExcelReportService, private val projectRepository: ProjectRepository) { | |||||
| class ReportController(private val invoiceRepository: InvoiceRepository, private val milestonePaymentRepository: MilestonePaymentRepository, private val excelReportService: ReportService, private val projectRepository: ProjectRepository, private val invoiceService: InvoiceService) { | |||||
| @PostMapping("/EX02-ProjectCashFlowReport") | |||||
| @PostMapping("/ProjectCashFlowReport") | |||||
| @Throws(ServletRequestBindingException::class, IOException::class) | @Throws(ServletRequestBindingException::class, IOException::class) | ||||
| fun getEx02ProjectCashFlowReport(@RequestBody @Valid request: EX02ProjectCashFlowReportRequest): ResponseEntity<Resource> { | |||||
| fun getProjectCashFlowReport(@RequestBody @Valid request: ProjectCashFlowReportRequest): ResponseEntity<Resource> { | |||||
| val project = projectRepository.findById(request.projectId).orElseThrow() | val project = projectRepository.findById(request.projectId).orElseThrow() | ||||
| val invoices = invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) | |||||
| val reportResult: ByteArray = excelReportService.generateEX02ProjectCashFlowReport(project) | |||||
| val reportResult: ByteArray = excelReportService.generateProjectCashFlowReport(project, invoices) | |||||
| // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | // val mediaType: MediaType = MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") | ||||
| return ResponseEntity.ok() | return ResponseEntity.ok() | ||||
| // .contentType(mediaType) | // .contentType(mediaType) | ||||
| .header("filename", "EX02 - Project Cash Flow Report - " + LocalDate.now() + ".xlsx") | |||||
| .header("filename", "Project Cash Flow Report - " + LocalDate.now() + ".xlsx") | |||||
| .body(ByteArrayResource(reportResult)) | .body(ByteArrayResource(reportResult)) | ||||
| } | } | ||||
| @GetMapping("/test/{id}") | |||||
| fun test(@PathVariable id: Long): List<Invoice> { | |||||
| val project = projectRepository.findById(id).orElseThrow() | |||||
| return invoiceService.findAllByProjectAndPaidAmountIsNotNull(project) | |||||
| } | |||||
| } | } | ||||
| @@ -1,5 +1,5 @@ | |||||
| package com.ffii.tsms.modules.report.web.model | package com.ffii.tsms.modules.report.web.model | ||||
| data class EX02ProjectCashFlowReportRequest ( | |||||
| data class ProjectCashFlowReportRequest ( | |||||
| val projectId: Long | val projectId: Long | ||||
| ) | ) | ||||
| @@ -0,0 +1,39 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:create invoice | |||||
| CREATE TABLE `invoice` ( | |||||
| `id` INT NOT NULL AUTO_INCREMENT, | |||||
| `version` INT NOT NULL DEFAULT '0', | |||||
| `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `createdBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, | |||||
| `modifiedBy` VARCHAR(30) NULL DEFAULT NULL, | |||||
| `deleted` TINYINT(1) NOT NULL DEFAULT '0', | |||||
| `invoiceNo` VARCHAR(45) NOT NULL, | |||||
| `projectId` INT NOT NULL, | |||||
| `milestonePaymentId` INT NOT NULL, | |||||
| `clientId` INT NOT NULL, | |||||
| `invoiceDate` DATE NOT NULL, | |||||
| `dueDate` DATE NOT NULL, | |||||
| `receiptDate` DATE NULL, | |||||
| `unpaidAmount` DECIMAL(16,2) NOT NULL, | |||||
| `paidAmount` DECIMAL(16,2) NULL, | |||||
| PRIMARY KEY (`id`), | |||||
| INDEX `FK_INVOICE_ON_PROJECTID` (`projectId` ASC) VISIBLE, | |||||
| INDEX `FK_INVOICE_ON_MILESTONEPAYMENTID` (`milestonePaymentId` ASC) VISIBLE, | |||||
| INDEX `FK_INVOICE_ON_CLIENTID` (`clientId` ASC) VISIBLE, | |||||
| CONSTRAINT `FK_INVOICE_ON_PROJECTID` | |||||
| FOREIGN KEY (`projectId`) | |||||
| REFERENCES `project` (`id`) | |||||
| ON DELETE NO ACTION | |||||
| ON UPDATE NO ACTION, | |||||
| CONSTRAINT `FK_INVOICE_ON_MILESTONEPAYMENTID` | |||||
| FOREIGN KEY (`milestonePaymentId`) | |||||
| REFERENCES `milestone_payment` (`id`) | |||||
| ON DELETE NO ACTION | |||||
| ON UPDATE NO ACTION, | |||||
| CONSTRAINT `FK_INVOICE_ON_CLIENTID` | |||||
| FOREIGN KEY (`clientId`) | |||||
| REFERENCES `customer` (`id`) | |||||
| ON DELETE NO ACTION | |||||
| ON UPDATE NO ACTION); | |||||
| @@ -0,0 +1,9 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:update invoice | |||||
| ALTER TABLE `invoice` | |||||
| DROP FOREIGN KEY `FK_INVOICE_ON_CLIENTID`; | |||||
| ALTER TABLE `invoice` | |||||
| DROP COLUMN `clientId`, | |||||
| DROP INDEX `FK_INVOICE_ON_CLIENTID` ; | |||||
| ; | |||||
| @@ -0,0 +1,9 @@ | |||||
| -- liquibase formatted sql | |||||
| -- changeset cyril:update invoice | |||||
| ALTER TABLE `invoice` | |||||
| DROP FOREIGN KEY `FK_INVOICE_ON_PROJECTID`; | |||||
| ALTER TABLE `invoice` | |||||
| DROP COLUMN `projectId`, | |||||
| DROP INDEX `FK_INVOICE_ON_PROJECTID` ; | |||||
| ; | |||||