Skip to content

Commit

Permalink
#7 #18 [back][front] added transfer_create endpoint and empty transfe…
Browse files Browse the repository at this point in the history
…rs view
  • Loading branch information
vityaman committed Jan 22, 2024
1 parent c88bc23 commit 93d5a36
Show file tree
Hide file tree
Showing 13 changed files with 169 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import zio.logging.backend.SLF4J
import ru.vityaman.mylogistics.api.http._
import ru.vityaman.mylogistics.data.jdbc._
import ru.vityaman.mylogistics.logic.service.basic._
import ru.vityaman.mylogistics.logic.service.logged.LoggedTransactionService

object MyLogistics extends ZIOAppDefault {
override def run: RIO[ZIOAppArgs & Scope, Nothing] = (for {
Expand Down Expand Up @@ -35,7 +36,7 @@ object MyLogistics extends ZIOAppDefault {

// Service
BasicUserService.layer,
BasicTransactionService.layer,
(BasicTransactionService.layer >>> LoggedTransactionService.layer),
BasicStorageService.layer,

// API
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package ru.vityaman.mylogistics.api.http

import zio._
import zio.http.Method.GET
import zio.http.Method.{GET, POST}
import zio.http._
import zio.json._

import ru.vityaman.mylogistics.api.http.request.CreateTransferRequest
import ru.vityaman.mylogistics.api.http.view.DetailedTransactionView
import ru.vityaman.mylogistics.api.http.view.EquippedTransferView
import ru.vityaman.mylogistics.logic.service.TransactionService

import java.lang.IllegalArgumentException

class TransactionApi(service: TransactionService) {
def routes: Routes[Any, Response] = Routes(
GET / "api" / "transaction" ->
Expand All @@ -26,6 +29,16 @@ class TransactionApi(service: TransactionService) {
.map(_.map(EquippedTransferView.fromModel))
.map(_.toJsonPretty)
.mapBoth(Response.fromThrowable(_), Response.json(_))
},
POST / "api" / "transfer" ->
handler { (request: Request) =>
request.body.asString
.map(_.fromJson[CreateTransferRequest])
.flatMap(ZIO.fromEither(_))
.mapError(_ => new IllegalArgumentException("invalid"))
.map(_.asModel)
.flatMap(service.create(_))
.mapBoth(Response.fromThrowable(_), id => Response.text(id.toString))
}
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ru.vityaman.mylogistics.api.http.request

import zio.json._

import ru.vityaman.mylogistics.logic.model.Transfer

import java.time.Instant

final case class CreateTransferRequest(
sourceId: Int,
targetId: Int,
withdrawMoment: Instant,
incomeMoment: Instant
) {
def asModel: Transfer.Request =
Transfer.Request(
sourceId = sourceId,
targetId = targetId,
withdrawMoment = withdrawMoment,
incomeMoment = incomeMoment
)
}

object CreateTransferRequest {
implicit val decoder: JsonDecoder[CreateTransferRequest] =
DeriveJsonDecoder.gen[CreateTransferRequest]
implicit val encoder: JsonEncoder[CreateTransferRequest] =
DeriveJsonEncoder.gen[CreateTransferRequest]
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ import ru.vityaman.mylogistics.logic.model.Transfer
trait TransactionRepository {
def getAll(): Task[List[DetailedTransaction]]
def getTransfers(): Task[List[Transfer.Equipped]]
def create(transfer: Transfer.Request): Task[Transfer.Id]
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import ru.vityaman.mylogistics.logic.model.Pack
import ru.vityaman.mylogistics.logic.model.Storage
import ru.vityaman.mylogistics.logic.model.Transfer

import java.sql.Timestamp

import doobie.Transactor
import doobie.implicits._
import doobie.implicits.javasql._
Expand Down Expand Up @@ -58,42 +60,71 @@ private class JdbcTransactionRepository(xa: Transactor[Task])
FROM transfer
JOIN storage AS source ON source.id = transfer.source_id
JOIN storage AS target ON target.id = transfer.target_id
JOIN transfer_atom ON transfer_atom.transfer_id = transfer.id
JOIN item_kind ON transfer_atom.item_kind_id = item_kind.id
LEFT JOIN transfer_atom ON transfer_atom.transfer_id = transfer.id
LEFT JOIN item_kind ON transfer_atom.item_kind_id = item_kind.id
"""
.query[DetailedTransferRow]
.stream
.transact(xa)
.compile
.toList
.map { rows =>
rows
.map(row =>
Transfer.Detailed(
id = row.transferId,
source = Storage.Brief(
id = row.sourceId,
name = row.sourceName
),
target = Storage.Brief(
id = row.targetId,
name = row.targetName
),
withdrawMoment = row.withdrawMoment.toInstant,
incomeMoment = row.incomeMoment.toInstant
) -> Pack(
itemKind = ItemKind(
id = row.itemKindId,
name = row.itemKindName,
unit = row.itemKindUnit
),
amount = Amount(row.amount)
)
.map(_.partition(_.isEmpty))
.map { case (empties: List[_], others: List[_]) =>
empties
.map(row => asTransfer(row) -> List[Pack]())
.concat(
others
.map(row => asTransfer(row) -> asPack(row).get)
.groupMap(_._1)(_._2)
.toList
)
.groupMap(_._1)(_._2)
.toList
.map { case (info, packs) => Transfer.Equipped(info, packs) }
}

override def create(transfer: Transfer.Request): Task[Transfer.Id] =
sql"""
SELECT transfer_create(
${transfer.sourceId},
${transfer.targetId},
${Timestamp.from(transfer.withdrawMoment)},
${Timestamp.from(transfer.incomeMoment)},
3
)
"""
.query[Int]
.unique
.transact(xa)

private def asTransfer(row: DetailedTransferRow): Transfer.Detailed =
Transfer.Detailed(
id = row.transferId,
source = Storage.Brief(
id = row.sourceId,
name = row.sourceName
),
target = Storage.Brief(
id = row.targetId,
name = row.targetName
),
withdrawMoment = row.withdrawMoment.toInstant,
incomeMoment = row.incomeMoment.toInstant
)

private def asPack(row: DetailedTransferRow): Option[Pack] =
if (!row.isEmpty) {
Some(
Pack(
itemKind = ItemKind(
id = row.itemKindId.get,
name = row.itemKindName.get,
unit = row.itemKindUnit.get
),
amount = Amount(row.amount.get)
)
)
} else {
None
}
}

object JdbcTransactionRepository {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package ru.vityaman.mylogistics.data.jdbc.row

import java.sql.Timestamp

/** @note
* `itemKind` and `amount` are optional as can be absent.
*/
final case class DetailedTransferRow(
transferId: Int,
withdrawMoment: Timestamp,
Expand All @@ -10,8 +13,19 @@ final case class DetailedTransferRow(
sourceName: String,
targetId: Int,
targetName: String,
itemKindId: Int,
itemKindName: String,
itemKindUnit: String,
amount: Int
)
itemKindId: Option[Int],
itemKindName: Option[String],
itemKindUnit: Option[String],
amount: Option[Int]
) {
assert(
!(
itemKindId.isDefined ^
itemKindName.isDefined ^
itemKindUnit.isDefined ^
amount.isDefined
)
)

def isEmpty: Boolean = itemKindId.isEmpty
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,12 @@ object Transfer {
packs: Pack.Set
)

final case class Request(
sourceId: Storage.Id,
targetId: Storage.Id,
withdrawMoment: Instant,
incomeMoment: Instant
)

type Id = Int
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ import ru.vityaman.mylogistics.logic.model.Transfer
trait TransactionService {
def getAll(): Task[List[DetailedTransaction]]
def getTransfers(): Task[List[Transfer.Equipped]]
def create(transfer: Transfer.Request): Task[Transfer.Id]
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class BasicTransactionService(repository: TransactionRepository)

override def getTransfers(): Task[List[Transfer.Equipped]] =
repository.getTransfers()

override def create(transfer: Transfer.Request): Task[Transfer.Id] =
repository.create(transfer)
}

object BasicTransactionService {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ru.vityaman.mylogistics.logic.service.logged

import zio.RLayer
import zio.Task
import zio.ZIO
import zio.ZLayer

import ru.vityaman.mylogistics.logic.model.DetailedTransaction
import ru.vityaman.mylogistics.logic.model.Transfer
import ru.vityaman.mylogistics.logic.service.TransactionService

final class LoggedTransactionService(origin: TransactionService)
extends TransactionService {

override def getAll(): Task[List[DetailedTransaction]] =
origin.getAll()

override def getTransfers(): Task[List[Transfer.Equipped]] =
origin
.getTransfers()
.tapErrorCause(ZIO.logCause(_))

override def create(transfer: Transfer.Request): Task[Transfer.Id] =
origin
.create(transfer)
.tapErrorCause(ZIO.logCause(_))
}

object LoggedTransactionService {
val layer: RLayer[TransactionService, TransactionService] =
ZLayer.fromFunction(new LoggedTransactionService(_))
}
2 changes: 1 addition & 1 deletion database/sql/logic/transfer/transfer_create.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ DECLARE
manager_id integer;
transfer_id integer;
BEGIN
SELECT id INTO manager_id
SELECT id INTO STRICT manager_id
FROM manager
WHERE manager.user_id = initiator_id;

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/main/scala/ru/vityaman/mylogistics/Api.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ object API {
}

object Transfer {
def getAll(): Future[List[Transfer]] =
def getAll(): Future[List[Transfer]] =
dom.ext.Ajax
.get(s"${base}/transfer")
.map(xhr => read[List[Transfer]](xhr.responseText))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ object TransferList {
margin := "0%",
td(child.text <-- signal.map(_.itemKind.name)),
td(child.text <-- signal.map(_.amount)),
td(child.text <-- signal.map(_.itemKind.name))
td(child.text <-- signal.map(_.itemKind.unit))
)
})
)
Expand Down

0 comments on commit 93d5a36

Please sign in to comment.