package com.lemans.services.document import com.lemans.builders.CciPdfBuilder import com.lemans.builders.CommercialInvoiceBuilder import com.lemans.builders.CooPdfBuilder import com.lemans.builders.PackingListBuilder import com.lemans.builders.SedPdfBuilder import com.lemans.builders.SkcooPdfBuilder import com.lemans.builders.UsmcaPdfBuilder import com.lemans.entities.Invoice import com.lemans.entities.InvoiceRepository import com.lemans.entities.ShipmentCartonRepository import com.lemans.entities.document.Document import com.lemans.entities.document.DocumentClassificationRepository import com.lemans.entities.document.DocumentEntity import com.lemans.entities.document.DocumentEntityRepository import com.lemans.entities.document.DocumentRepository import com.lemans.services.LemansManager import com.lemans.services.order.InvoiceDocumentService import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import java.sql.Timestamp import java.time.LocalDateTime @Service @Transactional class DocumentManagerService extends LemansManager { private static final List NO_FORM_DOCUMENTS = ['Commercial Invoice', 'Packing List', 'Proforma Invoice'] @Autowired InvoiceRepository invoiceRepository @Autowired DocumentRepository documentRepository @Autowired DocumentEntityRepository documentEntityRepository @Autowired InvoiceDocumentService invoiceDocumentService @Autowired DocumentClassificationRepository documentClassificationRepository @Autowired DocumentService documentService @Autowired String mediaPath @Autowired String mediaPrefix @Autowired String temporaryMediaPath @Autowired ShipmentCartonRepository shipmentCartonRepository void createDocument(String entityClass, String entityId, Integer documentClassificationId, String username, List errors, String usernameOnly) { Map userDetails = userDetails(usernameOnly) if (!userDetails.documentUrl) { errors << 'Signature not found, Please add signature before generating document' return } deleteExistingDocuments(entityClass, entityId, documentClassificationId, username) String classificationName = classificationName(documentClassificationId) if (classificationName in NO_FORM_DOCUMENTS) { try { List pickingRouteIds = getPickingRouteIds(entityId, entityClass) generateDocumentPerInvoice(pickingRouteIds, entityClass, entityId, documentClassificationId, classificationName, userDetails, errors) } catch(Exception e) { errors << e.printStackTrace() 'Error creating document' } } else { try { generateDocumentPerGroupOrInvoice(entityClass, entityId, documentClassificationId, classificationName, userDetails, errors) } catch(Exception e) { e.printStackTrace() errors << 'Error creating document' } } } void generateDocumentPerGroupOrInvoice(String entityClass, String entityId, Integer documentClassificationId, String classificationName, Map userDetails, List errors) { byte[] file Map data = invoiceDocumentService.generic(entityId, entityClass, classificationName) if (data) { buildFooter(data) switch (classificationName) { case 'US MCA': UsmcaPdfBuilder usmcaPdfBuilder = new UsmcaPdfBuilder() file = usmcaPdfBuilder.usmcaPdf(data, userDetails) break case 'South Korea Certificate of Origin': SkcooPdfBuilder skcooPdfBuilder = new SkcooPdfBuilder() file = skcooPdfBuilder.skcooPdf(data, userDetails) break case 'Certificate of Origin': CooPdfBuilder cooPdfBuilder = new CooPdfBuilder() file = cooPdfBuilder.cooPdf(data, userDetails, errors) break case 'Canada Commercial Invoice (CCI)': CciPdfBuilder cciPdfBuilder = new CciPdfBuilder() file = cciPdfBuilder.cciPdf(data, userDetails) break case 'SED shippers Export Declaration': SedPdfBuilder sedPdfBuilder = new SedPdfBuilder() file = sedPdfBuilder.sedPdf(data, userDetails) break default: break } } if (file && !errors) { String documentName = "${entityId}_${classificationName}" createAssociations(documentName, documentClassificationId, userDetails?.userName?.toString(), entityClass, entityId, file) } } void generateDocumentPerInvoice(List pickingRouteIds, String entityClass, String entityId, Integer documentClassificationId, String classificationName, Map userDetails, List errors) { byte[] file Map data = invoiceDocumentService.generic(entityId, entityClass, classificationName) if (data) { data?.header[0]?.trackingNumbers = shipmentCartonRepository.findAllByEntityClassAndEntityIdAndDateDeletedIsNull(entityClass, entityId)*.trackingNumber?.toList()?.join(', ') Map mainBody = data?.body1?.groupBy { it.pickingRouteId } mainBody.eachWithIndex { pickingRouteId,values, idx -> buildDocumentData(data, pickingRouteId, values, idx) switch (classificationName) { case 'Commercial Invoice': CommercialInvoiceBuilder invoiceBuilder = new CommercialInvoiceBuilder() data.header[0].groupId = (entityClass == 'group' ? entityId : null) file = invoiceBuilder.invoicePdf(data, false, userDetails, errors) break case 'Packing List': PackingListBuilder packingListBuilder = new PackingListBuilder() file = packingListBuilder.packingListPdf(data) break case 'Proforma Invoice': CommercialInvoiceBuilder invoiceBuilder = new CommercialInvoiceBuilder() file = invoiceBuilder.invoicePdf(data, true, userDetails, errors) break default: break } if (file && !errors) { String documentName = "${pickingRouteId}_${classificationName}" createAssociations(documentName, documentClassificationId, userDetails?.userName?.toString(), 'invoice', pickingRouteId, file) } } } } void buildDocumentData(Map data, String pickingRouteId, List values, Integer idx) { data.header[0].pickingRouteId = pickingRouteId Invoice invoice = invoiceRepository.findByIdAndDateDeletedIsNull(pickingRouteId) data.header[0].salesOrderNumber = invoice.salesOrderNumber data.header[0].customerReference = invoice.poNumber data.body1 = values data.body2 = values.groupBy {[country: it.countryName, harmonizeCode: it.harmonizeCode, harmonizeCodeDescr: it.harmonizeCodeUSDescr] }?.collect {k, v -> [harmonizeCode: k?.harmonizeCode?.toString(), lineNumber: v*.lineNumber?.join(', '), harmonizeCodeDescr: k.harmonizeCodeDescr, total: v.collect{ ((it.unitPrice ?: it.baseDealerPrice) ?: 0) * (it.shippedQuantity ?: 0) }.sum(), country: k.country] } BigDecimal invoiceTotalAmount = 0 BigDecimal otherCharges = 0 BigDecimal mdseTotal = values.amount.findAll { it != null }.sum() != null ? values.amount.findAll { it != null }.sum() : 0 BigDecimal customsTotal = values.collect { ((it.unitPrice ?: it.baseDealerPrice) ?: 0) * (it.shippedQuantity ?: 0) }.sum() if(data.header[0].noFreightOnInvoiceInd == null || data.header[0].noFreightOnInvoiceInd == false && idx == 0) { invoiceTotalAmount = mdseTotal + (data.header[0].freightCost != null ? data.header[0].freightCost : 0) otherCharges = (data.header[0].freightCost != null ? data.header[0].freightCost : 0) } else { invoiceTotalAmount = mdseTotal otherCharges = 0 } data.footer = [mdseTotal: mdseTotal, invoiceTotalAmount: invoiceTotalAmount, units: values.shippedQuantity.findAll { it != null }.sum(), otherCharges: otherCharges, customsTotal: customsTotal] } void buildFooter(Map data) { BigDecimal invoiceTotalAmount BigDecimal otherCharges BigDecimal mdseTotal = data.body1.amount.findAll { it != null }.sum() ?: 0 if(data.header[0].noFreightOnInvoiceInd == null || data.header[0].noFreightOnInvoiceInd == false) { invoiceTotalAmount = mdseTotal + (data.header[0].freightCost ?: 0) otherCharges = data.header[0].freightCost } else { invoiceTotalAmount = mdseTotal otherCharges = 0 } data.footer = [mdseTotal: mdseTotal, invoiceTotalAmount: invoiceTotalAmount, units: data.body1.shippedQuantity.findAll { it != null }.sum(), otherCharges: otherCharges] } void createAssociations(String documentName, Integer documentClassificationId, String username, String entityClass, String entityId, byte[] file) { String documentUUID = UUID.randomUUID() String documentUrl = "${mediaPrefix}/${documentUUID}" Path path = Paths.get("${mediaPath}${documentUrl}.pdf") Files.write(path, file) Document document = createDocument([documentUUID: documentUUID, documentUrl: documentUrl, documentName: documentName, documentClassificationId: documentClassificationId, username: username]) createDocumentEntity([entityClass: entityClass, entityId: entityId, documentId: document.id], username) } DocumentEntity addSignature(Map values, String usernameOnly, String username) { Integer classificationId = documentClassificationRepository.findByIdAndDateDeletedIsNull(values.documentClassificationId)?.id deletePreviousUserSignatureAssociations(values, classificationId, usernameOnly, username) DocumentEntity documentEntity String tempId = values.tempId values.remove('tempId') Document document = new Document(values) document.documentUrl = documentUrl(document) document.documentClassificationId = classificationId String tempFilePath = tempId + '.' + values.extension File tempFile = checkForTempFile(tempFilePath) documentRepository.saveOrDiscardDomain(document, username) if (!document.hasErrors()) { documentEntity = createDocumentEntity([entityId: usernameOnly, entityClass: 'Signature', documentId: document.id], username) if (!moveFile(tempFile, document)) { performSoftDelete(document, documentEntity, username) } } documentEntity } Map userDetails(String username) { Map userInfo = dqx([userName: username]).executeOneFrom('appSecurity.dbo.[user]', ['userName = :userName', "dealerCode = 'LEMANSCORP'"]) Map documentInfo = dqx([entityId: username]).executeOneFrom('export.vwDocumentEntity', ["entityClass = 'Signature'", 'entityId = :entityId']) if (documentInfo) { userInfo + [documentUrl: "${mediaPath}${documentInfo.documentUrl}.${documentInfo.extension}"] } else { userInfo + [documentUrl: null] } } private void deletePreviousUserSignatureAssociations(Map values, Integer classificationId, String usernameOnly, String username) { Document document = documentRepository.findByDocumentNameAndDocumentClassificationIdAndDateDeletedIsNull(values.documentName?.toString(), classificationId) if (document) { deleteExistingDocuments('Signature', usernameOnly, classificationId, username) } } private void performSoftDelete(Document document, DocumentEntity documentEntity, String username) { documentRepository.softDelete(document, username) documentEntityRepository.softDelete(documentEntity, username) documentEntity.errors.reject('file', 'Could not move uploaded file') } private String documentUrl(Document document) { mediaPrefix + File.separator + document.documentGuid } private File checkForTempFile(String tempFilePath) { String pathToTempFile = temporaryMediaPath + '/' + tempFilePath new File(pathToTempFile) } private boolean moveFile(File tempFile, Document document) { try { new File(completeMediaPath()).mkdir() File movedFile = new File(documentPath(document)) Files.copy(tempFile.toPath(), movedFile.toPath()) true } catch (Exception x) { false } } String documentPath(Document document) { "${mediaPath}${document.documentUrl}.${document.extension}" } private String completeMediaPath() { mediaPath + mediaPrefix } Document createDocument(Map data) { Document document = new Document() document.identity { it -> it.documentClassificationId = data.documentClassificationId it.documentUrl = data.documentUrl it.documentName = data.documentName it.documentGuid = data.documentUUID it.extension = 'pdf' } documentRepository.saveOrDiscardDomain(document, data.username?.toString()) } DocumentEntity createDocumentEntity(Map data, String username) { DocumentEntity documentEntity = new DocumentEntity() documentEntity.identity { it -> it.entityId = data.entityId it.entityClass = data.entityClass it.documentId = data.documentId } documentEntityRepository.saveOrDiscardDomain(documentEntity, username) } List getPickingRouteIds(String entityId, String entityClass) { entityClass == 'group' ? dqx([invoiceGroupId: entityId]).executeFrom('vwInvoicePickingRouteMaster', ['invoiceGroupId = :invoiceGroupId'])?.results?.pickingRouteId : dqx([pickingRouteId: entityId]).executeFrom('vwInvoicePickingRouteMaster', ['pickingRouteId = :pickingRouteId'])?.results?.pickingRouteId } String classificationName(Integer documentClassificationId) { dqx([documentClassificationId: documentClassificationId]).executeOneFrom("export.DocumentClassification", ['documentClassificationId = :documentClassificationId'])?.classificationName } Document deleteDocument(Integer documentId, String username) { Document document = documentRepository.findByIdAndDateDeletedIsNull(documentId) if (document) { File file = new File(documentPath(document)) if (file?.exists()) { file.delete() documentRepository.softDelete(document, username) sql().executeUpdate("""update export.documentEntity SET dateDeleted = GETDATE(), deletedBy = ${username} WHERE documentId = ${document.id}""") } else { document.errors.reject('document', 'document not found') } } document } private void deleteExistingDocuments(String entityClass, String entityId, Integer documentClassificationId, String username) { Timestamp now = new Timestamp(new Date().time) List existingDocuments = documentService.findAll([entityClass: entityClass, entityId: entityId, documentClassificationId: documentClassificationId])?.results List existingDocumentIds = existingDocuments?.documentId sql().withBatch(50, "update export.document SET dateDeleted = :now, deletedBy = :username WHERE documentId = :documentId AND dateDeleted IS NULL") { stmt -> existingDocumentIds.each { Integer documentId -> stmt.addBatch([now: now, username: username, documentId: documentId]) } } sql().withBatch(50, "update export.documentEntity SET dateDeleted = :now, deletedBy = :username WHERE entityClass = :entityClass AND entityId = :entityId AND documentId = :documentId AND dateDeleted IS NULL") { stmt -> existingDocumentIds.each { Integer documentId -> stmt.addBatch([now: now, username: username, documentId: documentId, entityId: entityId, entityClass: entityClass]) } } existingDocuments.each { Map it -> File file = new File("${it.documentUrl}.${it.extension}") if (file.exists()) { file.delete() } } } }