-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pos-print: add async receipt printing
- Loading branch information
Showing
17 changed files
with
800 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
pos-print/src/main/java/com/clouway/pos/print/adapter/db/PersistentReceiptRepository.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package com.clouway.pos.print.adapter.db | ||
|
||
import com.clouway.pos.print.core.* | ||
import com.google.inject.Inject | ||
import com.google.inject.Provider | ||
import com.mongodb.client.MongoCollection | ||
import com.mongodb.client.MongoDatabase | ||
import com.mongodb.client.model.Filters.eq | ||
import com.mongodb.client.model.Updates.set | ||
import org.bson.Document | ||
|
||
/** | ||
* @author Tsvetozar Bonev ([email protected]) | ||
*/ | ||
class PersistentReceiptRepository @Inject constructor(private val database: Provider<MongoDatabase>) | ||
: ReceiptRepository { | ||
|
||
private val collectionName: String = "receipts" | ||
|
||
override fun register(receipt: Receipt): String { | ||
if (receipts().find(eq("receiptId", receipt.receiptId)).firstOrNull() != null) | ||
throw ReceiptAlreadyInQueueException() | ||
|
||
val receiptDoc = receipt.toDocument() | ||
|
||
receipts().insertOne(receiptDoc) | ||
|
||
return receipt.receiptId | ||
} | ||
|
||
override fun getStatus(receiptId: String): ReceiptStatus { | ||
val receiptDoc = receipts().find( | ||
eq("receiptId", receiptId)).firstOrNull() | ||
?: throw ReceiptNotInQueueException() | ||
|
||
return ReceiptStatus.valueOf(receiptDoc.getString("status")) | ||
} | ||
|
||
override fun getByStatus(receiptStatus: ReceiptStatus): List<Receipt> { | ||
val receiptDocs = receipts().find(eq("status", receiptStatus.name)) | ||
|
||
val receiptList = mutableListOf<Receipt>() | ||
|
||
receiptDocs.forEach { | ||
receiptList.add(it.toReceipt()) | ||
} | ||
|
||
return receiptList | ||
} | ||
|
||
override fun finishPrinting(receiptId: String): Receipt { | ||
val receiptDoc = receipts().findOneAndUpdate( | ||
eq("receiptId", receiptId), | ||
set("status", ReceiptStatus.PRINTED.name)) | ||
?: throw ReceiptNotInQueueException() | ||
|
||
return receiptDoc.toReceipt() | ||
} | ||
|
||
override fun failPrinting(receiptId: String): Receipt { | ||
val receiptDoc = receipts().findOneAndUpdate( | ||
eq("receiptId", receiptId), | ||
set("status", ReceiptStatus.FAILED.name)) | ||
?: throw ReceiptNotInQueueException() | ||
|
||
return receiptDoc.toReceipt() | ||
} | ||
|
||
private fun receipts(): MongoCollection<Document> { | ||
return database.get().getCollection(collectionName) | ||
} | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
private fun Document.toReceipt(): Receipt { | ||
val receipt = Receipt.Builder() | ||
.withReceiptId(this.getString("receiptId")) | ||
.withAmount(this.getDouble("amount")) | ||
.currency(this.getString("currency")) | ||
.prefixLines(this["prefixLines"] as List<String>) | ||
.suffixLines(this["suffixLines"] as List<String>) | ||
|
||
val itemList = this["receiptItems"] as List<Document> | ||
|
||
itemList.forEach { | ||
receipt.addItem(it.toReceiptItem()) | ||
} | ||
|
||
return receipt.build() | ||
} | ||
|
||
private fun Document.toReceiptItem(): ReceiptItem{ | ||
return ReceiptItem.newItem() | ||
.name(this.getString("name")) | ||
.price(this.getDouble("price")) | ||
.quantity(this.getDouble("quantity")) | ||
.build() | ||
} | ||
|
||
private fun Receipt.toDocument(): Document{ | ||
val itemList = this.receiptItems | ||
|
||
val docList = mutableListOf<Document>() | ||
|
||
itemList.forEach { | ||
docList.add(it.toDocument()) | ||
} | ||
|
||
return Document() | ||
.append("receiptId", this.receiptId) | ||
.append("amount", this.amount) | ||
.append("prefixLines", this.prefixLines()) | ||
.append("suffixLines", this.suffixLines()) | ||
.append("currency", this.currency) | ||
.append("receiptItems", docList) | ||
.append("status", ReceiptStatus.PRINTING.name) | ||
} | ||
|
||
private fun ReceiptItem.toDocument(): Document{ | ||
return Document() | ||
.append("name", this.name) | ||
.append("price", this.price) | ||
.append("quantity", this.quantity) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
54 changes: 54 additions & 0 deletions
54
pos-print/src/main/java/com/clouway/pos/print/adapter/http/PrintServiceV2.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.clouway.pos.print.adapter.http | ||
|
||
import com.clouway.pos.print.core.* | ||
import com.clouway.pos.print.transport.GsonTransport | ||
import com.google.inject.name.Named | ||
import com.google.sitebricks.At | ||
import com.google.sitebricks.headless.Reply | ||
import com.google.sitebricks.headless.Request | ||
import com.google.sitebricks.headless.Service | ||
import com.google.sitebricks.http.Get | ||
import com.google.sitebricks.http.Post | ||
import org.slf4j.LoggerFactory | ||
import javax.inject.Inject | ||
|
||
/** | ||
* @author Tsvetozar Bonev ([email protected]) | ||
*/ | ||
@Service | ||
@At("/v2/receipts/req/print") | ||
class PrintServiceV2 @Inject constructor(private var repository: ReceiptRepository, | ||
private var queue: PrintQueue) { | ||
private val logger = LoggerFactory.getLogger(PrintServiceV2::class.java) | ||
|
||
@Post | ||
fun printReceipt(request: Request): Reply<*> { | ||
return try { | ||
val dto = request.read(ReceiptDTO::class.java).`as`(GsonTransport::class.java) | ||
val receiptId = repository.register(dto.receipt) | ||
|
||
queue.queue(PrintReceiptRequest(dto.receipt, dto.sourceIp, dto.fiscal)) | ||
|
||
logger.info("Receipt queued for printing") | ||
Reply.with(receiptId).ok() | ||
} catch (ex: ReceiptAlreadyInQueueException) { | ||
logger.error("Receipt is already in queue") | ||
Reply.saying<Int>().badRequest() | ||
} | ||
} | ||
|
||
@Get | ||
@At("/status/:id") | ||
fun getReceiptStatus(@Named("id") id: String): Reply<*> { | ||
return try { | ||
val receiptStatus = repository.getStatus(id) | ||
logger.info("Receipt status returned as ${receiptStatus.name}") | ||
Reply.with(receiptStatus).ok() | ||
}catch (ex: ReceiptNotInQueueException){ | ||
logger.error("Receipt not found") | ||
Reply.saying<Int>().notFound() | ||
} | ||
} | ||
|
||
internal data class ReceiptDTO(val sourceIp: String = "", val operatorId: String = "", val fiscal: Boolean = false, val receipt: Receipt = Receipt.newReceipt().build()) | ||
} |
78 changes: 78 additions & 0 deletions
78
pos-print/src/main/java/com/clouway/pos/print/core/BackgroundReceiptPrintWorker.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package com.clouway.pos.print.core | ||
|
||
import com.clouway.pos.print.printer.Status | ||
import com.google.common.util.concurrent.AbstractExecutionThreadService | ||
import com.google.inject.Inject | ||
import org.slf4j.LoggerFactory | ||
import java.io.IOException | ||
|
||
/** | ||
* @author Tsvetozar Bonev ([email protected]) | ||
*/ | ||
class BackgroundReceiptPrintWorker @Inject constructor(private var repository: ReceiptRepository, | ||
private var factory: PrinterFactory, | ||
private var notifier: PrintNotifier, | ||
private var queue: PrintQueue) | ||
: ReceiptPrintWorker, AbstractExecutionThreadService() { | ||
|
||
override fun run() { | ||
while (isRunning){ | ||
printReceipts() | ||
} | ||
} | ||
|
||
private val logger = LoggerFactory.getLogger(ReceiptPrintWorker::class.java) | ||
|
||
override fun shutDown() { | ||
logger.info("Stopping background printing service") | ||
|
||
super.shutDown() | ||
} | ||
override fun printReceipts() { | ||
while (queue.hasNext()) { | ||
val receiptWithSource = queue.next() | ||
println(receiptWithSource) | ||
val receipt = receiptWithSource.receipt | ||
|
||
var printer: ReceiptPrinter? = null | ||
|
||
try { | ||
printer = factory.getPrinter(receiptWithSource.sourceIp) | ||
|
||
val printResponse = if (receiptWithSource.isFiscal) printer.printFiscalReceipt(receipt) | ||
else printer.printReceipt(receipt) | ||
|
||
if (printResponse.warnings.contains(Status.FISCAL_RECEIPT_IS_OPEN) | ||
|| printResponse.warnings.contains(Status.NON_FISCAL_RECEIPT_IS_OPEN)) { | ||
logger.info("Receipt printing accepted") | ||
repository.finishPrinting(receipt.receiptId) | ||
} else { | ||
logger.info("Receipt printing rejected") | ||
repository.failPrinting(receipt.receiptId) | ||
} | ||
|
||
notifier.notifyPrinted(printResponse) | ||
|
||
}catch (ex: ReceiptNotInQueueException) { | ||
logger.warn("Receipt was not found in queue") | ||
}catch (ex: DeviceNotFoundException) { | ||
logger.warn("Device was not found") | ||
repository.failPrinting(receipt.receiptId) | ||
println("Receipt is ${receipt.receiptId}") | ||
notifier.notifyPrinted(PrintReceiptResponse(setOf(Status.GENERAL_ERROR))) | ||
}catch (ex: IOException){ | ||
logger.warn("Printer threw IO Exception") | ||
repository.failPrinting(receipt.receiptId) | ||
notifier.notifyPrinted(PrintReceiptResponse(setOf(Status.GENERAL_ERROR))) | ||
}finally { | ||
printer?.close() | ||
} | ||
} | ||
} | ||
|
||
override fun start() { | ||
logger.info("Starting background printing service") | ||
|
||
this.startAsync() | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
pos-print/src/main/java/com/clouway/pos/print/core/ConsolePrintNotifier.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.clouway.pos.print.core | ||
|
||
/** | ||
* @author Tsvetozar Bonev ([email protected]) | ||
*/ | ||
class ConsolePrintNotifier : PrintNotifier { | ||
override fun notifyPrinted(printResponse: PrintReceiptResponse) { | ||
Thread.sleep(1000) | ||
println(printResponse) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ package com.clouway.pos.print.core | |
|
||
import com.clouway.pos.print.printer.FP705PrinterFactory | ||
import com.google.inject.AbstractModule | ||
import com.google.inject.Singleton | ||
|
||
/** | ||
* @author Martin Milev <[email protected]> | ||
|
@@ -10,5 +11,8 @@ class CoreModule : AbstractModule() { | |
|
||
override fun configure() { | ||
bind(PrinterFactory::class.java).to(FP705PrinterFactory::class.java) | ||
bind(PrintNotifier::class.java).to(ConsolePrintNotifier::class.java) | ||
bind(PrintQueue::class.java).to(InMemoryPrintQueue::class.java).`in`(Singleton::class.java) | ||
bind(ReceiptPrintWorker::class.java).to(BackgroundReceiptPrintWorker::class.java).`in`(Singleton::class.java) | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
pos-print/src/main/java/com/clouway/pos/print/core/InMemoryPrintQueue.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.clouway.pos.print.core | ||
|
||
import org.eclipse.jetty.util.BlockingArrayQueue | ||
|
||
/** | ||
* @author Tsvetozar Bonev ([email protected]) | ||
*/ | ||
class InMemoryPrintQueue : PrintQueue { | ||
|
||
private val queue = BlockingArrayQueue<PrintReceiptRequest>() | ||
|
||
/** | ||
* Block until queue has a next element. | ||
*/ | ||
override fun hasNext(): Boolean { | ||
while(true){ | ||
if(queue.peek() != null) return true | ||
else Thread.sleep(100) | ||
} | ||
} | ||
|
||
override fun next(): PrintReceiptRequest { | ||
return queue.poll() | ||
} | ||
|
||
override fun queue(printReceiptRequest: PrintReceiptRequest) { | ||
queue.offer(printReceiptRequest) | ||
} | ||
} |
Oops, something went wrong.