Compare commits

...

8 Commits

Author SHA1 Message Date
zyzf
a1b57f90fd back deployed to server 2023-12-28 15:04:42 +04:00
zyzf
e086b3a04b fixed cart errors 2023-12-28 01:56:21 +04:00
zyzf
ed634ebd21 fixed permissions for statistic screen 2023-12-26 13:51:30 +04:00
Zyzf
c74b39e692 final 2023-12-24 16:20:59 +04:00
zyzf
ebade00c96 moved to postgres db 2023-12-24 01:59:27 +04:00
Zyzf
9a9b066120 need to fix UNIQUE in spring 2023-12-23 23:53:41 +04:00
Zyzf
710b20c3af done with android app?? 2023-12-22 21:13:33 +04:00
zyzf
105378ca52 some done 2023-12-22 17:01:49 +04:00
69 changed files with 1635 additions and 150 deletions

View File

@ -3,20 +3,7 @@
<component name="deploymentTargetDropDown"> <component name="deploymentTargetDropDown">
<value> <value>
<entry key="app"> <entry key="app">
<State> <State />
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_7_API_33.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-12-20T08:37:35.519292927Z" />
</State>
</entry> </entry>
</value> </value>
</component> </component>

View File

@ -11,7 +11,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "com.zyzf.coffeepreorder" applicationId = "com.zyzf.coffeepreorder"
minSdk = 24 minSdk = 26
targetSdk = 33 targetSdk = 33
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.0"

View File

@ -2,7 +2,12 @@ package com.zyzf.coffeepreorder.api
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.zyzf.coffeepreorder.api.model.CoffeeRemote import com.zyzf.coffeepreorder.api.model.CoffeeRemote
import com.zyzf.coffeepreorder.api.model.OrderCoffeeCrossRefRemote
import com.zyzf.coffeepreorder.api.model.OrderCoffeeRemote
import com.zyzf.coffeepreorder.api.model.OrderRemote
import com.zyzf.coffeepreorder.api.model.UserRemote import com.zyzf.coffeepreorder.api.model.UserRemote
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -77,8 +82,44 @@ interface MyServerService {
@Path("id") id: Int, @Path("id") id: Int,
): CoffeeRemote ): CoffeeRemote
@GET("order/byDate")
suspend fun getOrdersByDate(
@Query("startDate") startDate: String,
@Query("endDate") endDate: String,
): List<OrderCoffeeRemote>
@POST("order/")
suspend fun createOrder(
@Body order: OrderRemote,
): OrderRemote
@GET("order/byUser")
suspend fun getOrdersByUser(
@Query("userId") userId: Int,
@Query("pageNo") page: Int,
@Query("pageSize") limit: Int,
): List<OrderRemote>
@GET("order/coffeesByOrder")
suspend fun getCoffeesByOrder(
@Query("orderId") orderId: Int,
): List<CoffeeRemote>
@GET("order/coffeeCrossRef")
suspend fun getOrderCoffees(): List<OrderCoffeeCrossRefRemote>
@POST("order/coffeeCrossRef")
suspend fun createOrderCoffee(
@Body orderCoffee: OrderCoffeeCrossRefRemote,
)
@DELETE("order/coffeeCrossRef/{id}")
suspend fun deleteOrderCoffee(
@Path("id") id: Int,
)
companion object { companion object {
private const val BASE_URL = "http://192.168.0.100:8080/api/" private const val BASE_URL = "https://api.zyzf.space/api/"
@Volatile @Volatile
private var INSTANCE: MyServerService? = null private var INSTANCE: MyServerService? = null

View File

@ -63,7 +63,7 @@ class CoffeeRemoteMediator(
val nextKey = if (endOfPaginationReached) null else page + 1 val nextKey = if (endOfPaginationReached) null else page + 1
val keys = coffees.map { val keys = coffees.map {
RemoteKeys( RemoteKeys(
entityId = it.uid, entityId = it.coffeeId,
type = RemoteKeyType.COFFEE, type = RemoteKeyType.COFFEE,
prevKey = prevKey, prevKey = prevKey,
nextKey = nextKey nextKey = nextKey
@ -83,14 +83,14 @@ class CoffeeRemoteMediator(
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Coffee>): RemoteKeys? { private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Coffee>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { coffee -> ?.let { coffee ->
dbRemoteKeyRepository.getAllRemoteKeys(coffee.uid, RemoteKeyType.COFFEE) dbRemoteKeyRepository.getAllRemoteKeys(coffee.coffeeId, RemoteKeyType.COFFEE)
} }
} }
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Coffee>): RemoteKeys? { private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Coffee>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { coffee -> ?.let { coffee ->
dbRemoteKeyRepository.getAllRemoteKeys(coffee.uid, RemoteKeyType.COFFEE) dbRemoteKeyRepository.getAllRemoteKeys(coffee.coffeeId, RemoteKeyType.COFFEE)
} }
} }
@ -98,7 +98,7 @@ class CoffeeRemoteMediator(
state: PagingState<Int, Coffee> state: PagingState<Int, Coffee>
): RemoteKeys? { ): RemoteKeys? {
return state.anchorPosition?.let { position -> return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { coffeeUid -> state.closestItemToPosition(position)?.coffeeId?.let { coffeeUid ->
dbRemoteKeyRepository.getAllRemoteKeys(coffeeUid, RemoteKeyType.COFFEE) dbRemoteKeyRepository.getAllRemoteKeys(coffeeUid, RemoteKeyType.COFFEE)
} }
} }

View File

@ -47,14 +47,14 @@ class RestCoffeeRepository(
service.getCoffee(uid).toCoffee() service.getCoffee(uid).toCoffee()
override suspend fun insert(coffee: Coffee): Long { override suspend fun insert(coffee: Coffee): Long {
return service.createCoffee(coffee.toCoffeeRemote()).toCoffee().uid.toLong() return service.createCoffee(coffee.toCoffeeRemote()).toCoffee().coffeeId.toLong()
} }
override suspend fun update(coffee: Coffee): Int { override suspend fun update(coffee: Coffee): Int {
return service.updateCoffee(coffee.uid, coffee.toCoffeeRemote()).toCoffee().uid return service.updateCoffee(coffee.coffeeId, coffee.toCoffeeRemote()).toCoffee().coffeeId
} }
override suspend fun delete(coffee: Coffee) { override suspend fun delete(coffee: Coffee) {
service.deleteCoffee(coffee.uid).toCoffee() service.deleteCoffee(coffee.coffeeId).toCoffee()
} }
} }

View File

@ -19,7 +19,7 @@ fun CoffeeRemote.toCoffee(): Coffee = Coffee(
) )
fun Coffee.toCoffeeRemote(): CoffeeRemote = CoffeeRemote( fun Coffee.toCoffeeRemote(): CoffeeRemote = CoffeeRemote(
uid, coffeeId,
name, name,
cost, cost,
ingredients ingredients

View File

@ -0,0 +1,23 @@
package com.zyzf.coffeepreorder.api.model
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
import kotlinx.serialization.Serializable
@Serializable
data class OrderCoffeeCrossRefRemote(
val orderId: Int = 0,
val coffeeId: Int = 0,
val count: Int = 0
)
fun OrderCoffeeCrossRefRemote.toOrderCoffeeCrossRef(): OrderCoffeeCrossRef = OrderCoffeeCrossRef(
orderId,
coffeeId,
count
)
fun OrderCoffeeCrossRef.toOrderCoffeeCrossRefRemote(): OrderCoffeeCrossRefRemote = OrderCoffeeCrossRefRemote(
orderId,
coffeeId,
count
)

View File

@ -0,0 +1,27 @@
package com.zyzf.coffeepreorder.api.model
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import kotlinx.serialization.Serializable
@Serializable
data class OrderCoffeeRemote(
val order: OrderRemote = OrderRemote(),
val coffees: List<Pair<CoffeeRemote, Int>> = listOf()
)
fun OrderCoffeeRemote.toOrderWithCoffees(): OrderWithCoffees {
val convertedOrder = this.order.toOrder()
val convertedCoffees: List<Coffee> = this.coffees.map { it.first.toCoffee() }
val convertedCounts: List<Int> = this.coffees.map { it.second }
return OrderWithCoffees(convertedOrder, order.user.toUser(), convertedCoffees, convertedCounts)
}
fun OrderWithCoffees.toOrderCoffeeRemote(): OrderCoffeeRemote {
val convertedOrder = this.order.toOrderRemote()
var convertedCoffees = ArrayList<Pair<CoffeeRemote, Int>>()
for (i in (0..this.coffees.count()-1)) {
convertedCoffees.add(Pair(this.coffees[i].toCoffeeRemote(), this.counts[i]))
}
return OrderCoffeeRemote(convertedOrder, convertedCoffees)
}

View File

@ -0,0 +1,43 @@
package com.zyzf.coffeepreorder.api.model
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.user.RestUserRepository
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.AppDataContainer
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import com.zyzf.coffeepreorder.database.model.User
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
@Serializable
data class OrderRemote(
val id: Int = 0,
val date: String = "",
val user: UserRemote = User.getUser().toUserRemote(),
val sum: Double = 0.0
)
fun OrderRemote.toOrder(): Order = Order(
id,
date,
user.id,
sum
)
fun Order.toOrderRemote(): OrderRemote = OrderRemote(
orderId,
date,
temp(userId),
sum
)
private fun temp(userId: Int): UserRemote {
var user: UserRemote? = null
runBlocking {
user = MyServerService.getInstance().getUser(userId)
}
return user!!
}

View File

@ -23,7 +23,7 @@ fun UserRemote.toUser(): User = User(
) )
fun User.toUserRemote(): UserRemote = UserRemote( fun User.toUserRemote(): UserRemote = UserRemote(
uid, userId,
login, login,
fio, fio,
phone, phone,

View File

@ -0,0 +1,111 @@
package com.zyzf.coffeepreorder.api.order
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import androidx.paging.RemoteMediator
import androidx.room.withTransaction
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.model.toCoffee
import com.zyzf.coffeepreorder.api.model.toOrder
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.RemoteKeyType
import com.zyzf.coffeepreorder.database.model.RemoteKeys
import com.zyzf.coffeepreorder.database.repository.OfflineCoffeeRepository
import com.zyzf.coffeepreorder.database.repository.OfflineOrderRepository
import com.zyzf.coffeepreorder.database.repository.OfflineRemoteKeyRepository
import retrofit2.HttpException
import java.io.IOException
@OptIn(ExperimentalPagingApi::class)
class OrderRemoteMediator(
private val service: MyServerService,
private val dbOrderRepository: OfflineOrderRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : RemoteMediator<Int, Order>() {
override suspend fun initialize(): InitializeAction {
return InitializeAction.LAUNCH_INITIAL_REFRESH
}
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, Order>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: 1
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
remoteKeys?.prevKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
remoteKeys?.nextKey
?: return MediatorResult.Success(endOfPaginationReached = remoteKeys != null)
}
}
try {
val orders = service.getOrdersByUser(CoffeeApplication.currentUser!!.userId, page, state.config.pageSize).map { it.toOrder() }
val endOfPaginationReached = orders.isEmpty()
database.withTransaction {
if (loadType == LoadType.REFRESH) {
dbRemoteKeyRepository.deleteRemoteKey(RemoteKeyType.ORDER)
dbOrderRepository.deleteAll()
}
val prevKey = if (page == 1) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val keys = orders.map {
RemoteKeys(
entityId = it.orderId,
type = RemoteKeyType.ORDER,
prevKey = prevKey,
nextKey = nextKey
)
}
dbRemoteKeyRepository.createRemoteKeys(keys)
dbOrderRepository.insertOrders(orders)
}
return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
} catch (exception: IOException) {
return MediatorResult.Error(exception)
} catch (exception: HttpException) {
return MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Order>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { order ->
dbRemoteKeyRepository.getAllRemoteKeys(order.orderId, RemoteKeyType.ORDER)
}
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, Order>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { order ->
dbRemoteKeyRepository.getAllRemoteKeys(order.orderId, RemoteKeyType.ORDER)
}
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, Order>
): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.orderId?.let { orderUid ->
dbRemoteKeyRepository.getAllRemoteKeys(orderUid, RemoteKeyType.ORDER)
}
}
}
}

View File

@ -0,0 +1,39 @@
package com.zyzf.coffeepreorder.api.order
import android.util.Log
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.model.toOrderCoffeeCrossRef
import com.zyzf.coffeepreorder.api.model.toOrderCoffeeCrossRefRemote
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
import com.zyzf.coffeepreorder.database.repository.OfflineOrderWithCoffeesRepository
import com.zyzf.coffeepreorder.database.repository.OrderWithCoffeesRepository
class RestOrderCoffeesRepository (
private val service: MyServerService,
private val dbOrderCoffeeRepository: OfflineOrderWithCoffeesRepository,
): OrderWithCoffeesRepository {
override suspend fun getAll(): List<OrderCoffeeCrossRef> {
Log.d(RestOrderCoffeesRepository::class.simpleName, "Get OrderCoffees")
val existOrderCoffees = dbOrderCoffeeRepository.getAll().toMutableList()
val serverOrderCoffees = service.getOrderCoffees().map { it.toOrderCoffeeCrossRef() }
// Найти записи для удаления (те, что есть в БД, но отсутствуют на сервере)
val toDelete = existOrderCoffees.filterNot { serverOrderCoffees.contains(it) }
toDelete.forEach { dbOrderCoffeeRepository.delete(it) }
// Найти новые записи для добавления (те, что есть на сервере, но отсутствуют в БД)
val toAdd = serverOrderCoffees.filterNot { existOrderCoffees.contains(it) }
toAdd.forEach { dbOrderCoffeeRepository.insert(it) }
// Вернуть обновленный список записей из БД
return dbOrderCoffeeRepository.getAll()
}
override suspend fun insert(orderCoffee: OrderCoffeeCrossRef) {
service.createOrderCoffee(orderCoffee.toOrderCoffeeCrossRefRemote())
}
override suspend fun delete(orderCoffee: OrderCoffeeCrossRef) {
service.deleteOrderCoffee(orderCoffee.orderId)
}
}

View File

@ -0,0 +1,62 @@
package com.zyzf.coffeepreorder.api.order
import android.util.Log
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.model.toCoffee
import com.zyzf.coffeepreorder.api.model.toOrder
import com.zyzf.coffeepreorder.api.model.toOrderRemote
import com.zyzf.coffeepreorder.api.model.toOrderWithCoffees
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import com.zyzf.coffeepreorder.database.repository.OfflineOrderRepository
import com.zyzf.coffeepreorder.database.repository.OfflineRemoteKeyRepository
import com.zyzf.coffeepreorder.database.repository.OrderRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.flowOf
class RestOrderRepository(
private val service: MyServerService,
private val dbOrderRepository: OfflineOrderRepository,
private val dbRemoteKeyRepository: OfflineRemoteKeyRepository,
private val database: AppDatabase
) : OrderRepository {
override suspend fun getCoffeesByOrder(orderId: Int): Flow<List<Coffee>> {
return flowOf(service.getCoffeesByOrder(orderId).map { it.toCoffee() })
}
override suspend fun getOrdersByDate(startDate: String, endDate: String): Flow<List<OrderWithCoffees>> {
return flowOf(service.getOrdersByDate(startDate, endDate).map { it.toOrderWithCoffees() })
}
@OptIn(ExperimentalPagingApi::class)
override fun getOrdersByUser(id: Int): Flow<PagingData<Order>> {
Log.d(RestOrderRepository::class.simpleName, "Get Orders by User")
val pagingSourceFactory = { dbOrderRepository.getOrdersByUserPagingSource(id) }
return Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
remoteMediator = OrderRemoteMediator(
service,
dbOrderRepository,
dbRemoteKeyRepository,
database
),
pagingSourceFactory = pagingSourceFactory
).flow
}
override suspend fun insert(order: Order): Long {
return service.createOrder(order.toOrderRemote()).toOrder().orderId.toLong()
}
}

View File

@ -44,21 +44,21 @@ class RestUserRepository(
} }
override suspend fun getByUid(uid: Int): User = override suspend fun getByUid(uid: Int): User =
service.getUser(uid).toUser()!! service.getUser(uid).toUser()
override suspend fun tryLogin(login: String, password: String): User? = override suspend fun tryLogin(login: String, password: String): User =
service.tryLogin(login, password).toUser() service.tryLogin(login, password).toUser()
override suspend fun insert(user: User): Long { override suspend fun insert(user: User): Long {
return service.createUser(user.toUserRemote()).toUser()?.uid?.toLong()!! return service.createUser(user.toUserRemote()).toUser().userId.toLong()
} }
override suspend fun update(user: User): Int { override suspend fun update(user: User): Int {
return service.updateUser(user.uid, user.toUserRemote()).toUser()?.uid!! return service.updateUser(user.userId, user.toUserRemote()).toUser().userId
} }
override suspend fun delete(user: User) { override suspend fun delete(user: User) {
service.deleteUser(user.uid).toUser() service.deleteUser(user.userId).toUser()
} }
} }

View File

@ -63,7 +63,7 @@ class UserRemoteMediator(
val nextKey = if (endOfPaginationReached) null else page + 1 val nextKey = if (endOfPaginationReached) null else page + 1
val keys = users.map { val keys = users.map {
RemoteKeys( RemoteKeys(
entityId = it.uid, entityId = it.userId,
type = RemoteKeyType.USER, type = RemoteKeyType.USER,
prevKey = prevKey, prevKey = prevKey,
nextKey = nextKey nextKey = nextKey
@ -83,14 +83,14 @@ class UserRemoteMediator(
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, User>): RemoteKeys? { private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, User>): RemoteKeys? {
return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull() return state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { user -> ?.let { user ->
dbRemoteKeyRepository.getAllRemoteKeys(user.uid, RemoteKeyType.USER) dbRemoteKeyRepository.getAllRemoteKeys(user.userId, RemoteKeyType.USER)
} }
} }
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, User>): RemoteKeys? { private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, User>): RemoteKeys? {
return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull() return state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { user -> ?.let { user ->
dbRemoteKeyRepository.getAllRemoteKeys(user.uid, RemoteKeyType.USER) dbRemoteKeyRepository.getAllRemoteKeys(user.userId, RemoteKeyType.USER)
} }
} }
@ -98,7 +98,7 @@ class UserRemoteMediator(
state: PagingState<Int, User> state: PagingState<Int, User>
): RemoteKeys? { ): RemoteKeys? {
return state.anchorPosition?.let { position -> return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.uid?.let { userUid -> state.closestItemToPosition(position)?.userId?.let { userUid ->
dbRemoteKeyRepository.getAllRemoteKeys(userUid, RemoteKeyType.USER) dbRemoteKeyRepository.getAllRemoteKeys(userUid, RemoteKeyType.USER)
} }
} }

View File

@ -3,10 +3,14 @@ package com.zyzf.coffeepreorder.database
import android.content.Context import android.content.Context
import com.zyzf.coffeepreorder.api.MyServerService import com.zyzf.coffeepreorder.api.MyServerService
import com.zyzf.coffeepreorder.api.coffee.RestCoffeeRepository import com.zyzf.coffeepreorder.api.coffee.RestCoffeeRepository
import com.zyzf.coffeepreorder.api.order.RestOrderCoffeesRepository
import com.zyzf.coffeepreorder.api.order.RestOrderRepository
import com.zyzf.coffeepreorder.api.user.RestUserRepository import com.zyzf.coffeepreorder.api.user.RestUserRepository
import com.zyzf.coffeepreorder.database.repository.CartRepository import com.zyzf.coffeepreorder.database.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.OfflineCartRepository import com.zyzf.coffeepreorder.database.repository.OfflineCartRepository
import com.zyzf.coffeepreorder.database.repository.OfflineCoffeeRepository import com.zyzf.coffeepreorder.database.repository.OfflineCoffeeRepository
import com.zyzf.coffeepreorder.database.repository.OfflineOrderRepository
import com.zyzf.coffeepreorder.database.repository.OfflineOrderWithCoffeesRepository
import com.zyzf.coffeepreorder.database.repository.OfflineRemoteKeyRepository import com.zyzf.coffeepreorder.database.repository.OfflineRemoteKeyRepository
import com.zyzf.coffeepreorder.database.repository.OfflineUserRepository import com.zyzf.coffeepreorder.database.repository.OfflineUserRepository
@ -14,6 +18,8 @@ interface AppContainer {
val cartRepository: CartRepository val cartRepository: CartRepository
val coffeeRestRepository: RestCoffeeRepository val coffeeRestRepository: RestCoffeeRepository
val userRestRepository: RestUserRepository val userRestRepository: RestUserRepository
val orderRestRepository: RestOrderRepository
val orderCoffeesRestRepository: RestOrderCoffeesRepository
companion object { companion object {
const val LIMIT = 10 const val LIMIT = 10
@ -27,6 +33,12 @@ class AppDataContainer(private val context: Context) : AppContainer {
private val userRepository: OfflineUserRepository by lazy { private val userRepository: OfflineUserRepository by lazy {
OfflineUserRepository(AppDatabase.getInstance(context).userDao()) OfflineUserRepository(AppDatabase.getInstance(context).userDao())
} }
private val orderRepository: OfflineOrderRepository by lazy {
OfflineOrderRepository(AppDatabase.getInstance(context).orderDao())
}
private val orderCoffeesRepository: OfflineOrderWithCoffeesRepository by lazy {
OfflineOrderWithCoffeesRepository(AppDatabase.getInstance(context).orderWithCoffeesDao())
}
override val cartRepository: CartRepository by lazy { override val cartRepository: CartRepository by lazy {
OfflineCartRepository(AppDatabase.getInstance(context).cartDao()) OfflineCartRepository(AppDatabase.getInstance(context).cartDao())
} }
@ -49,4 +61,18 @@ class AppDataContainer(private val context: Context) : AppContainer {
AppDatabase.getInstance(context) AppDatabase.getInstance(context)
) )
} }
override val orderRestRepository: RestOrderRepository by lazy {
RestOrderRepository(
MyServerService.getInstance(),
orderRepository,
remoteKeyRepository,
AppDatabase.getInstance(context)
)
}
override val orderCoffeesRestRepository: RestOrderCoffeesRepository by lazy {
RestOrderCoffeesRepository(
MyServerService.getInstance(),
orderCoffeesRepository
)
}
} }

View File

@ -6,18 +6,24 @@ import androidx.room.Room
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.zyzf.coffeepreorder.database.dao.CartDao import com.zyzf.coffeepreorder.database.dao.CartDao
import com.zyzf.coffeepreorder.database.dao.CoffeeDao import com.zyzf.coffeepreorder.database.dao.CoffeeDao
import com.zyzf.coffeepreorder.database.dao.OrderDao
import com.zyzf.coffeepreorder.database.dao.OrderWithCoffees
import com.zyzf.coffeepreorder.database.dao.RemoteKeysDao import com.zyzf.coffeepreorder.database.dao.RemoteKeysDao
import com.zyzf.coffeepreorder.database.dao.UserDao import com.zyzf.coffeepreorder.database.dao.UserDao
import com.zyzf.coffeepreorder.database.model.Cart import com.zyzf.coffeepreorder.database.model.Cart
import com.zyzf.coffeepreorder.database.model.Coffee import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
import com.zyzf.coffeepreorder.database.model.RemoteKeys import com.zyzf.coffeepreorder.database.model.RemoteKeys
import com.zyzf.coffeepreorder.database.model.User import com.zyzf.coffeepreorder.database.model.User
@Database(entities = [User::class, Coffee::class, Cart::class, RemoteKeys::class], version = 1, exportSchema = false) @Database(entities = [User::class, Coffee::class, Cart::class, Order::class, OrderCoffeeCrossRef::class, RemoteKeys::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao abstract fun userDao(): UserDao
abstract fun coffeeDao(): CoffeeDao abstract fun coffeeDao(): CoffeeDao
abstract fun cartDao(): CartDao abstract fun cartDao(): CartDao
abstract fun orderDao(): OrderDao
abstract fun orderWithCoffeesDao(): OrderWithCoffees
abstract fun remoteKeysDao(): RemoteKeysDao abstract fun remoteKeysDao(): RemoteKeysDao
companion object { companion object {

View File

@ -14,14 +14,17 @@ interface CartDao {
@Query("select * from cart") @Query("select * from cart")
suspend fun getAll(): Cart suspend fun getAll(): Cart
@Query("select coffee.uid, coffee.name, coffee.cost, coffee.ingredients from cart join coffee on coffee.uid = cart.coffee_id and cart.count > 0 collate nocase") @Query("select coffee.* from cart join coffee on coffee.coffee_id = cart.coffee_id and cart.count > 0 collate nocase")
fun getAllInCart(): PagingSource<Int, Coffee> fun getAllInCart(): PagingSource<Int, Coffee>
@Query("select sum(coffee.cost * cart.count) from cart JOIN coffee on coffee.uid = cart.coffee_id and cart.count > 0") @Query("select coffee.* from cart join coffee on coffee.coffee_id = cart.coffee_id and cart.count > 0 collate nocase")
suspend fun getAllInCartList(): List<Coffee>
@Query("select sum(coffee.cost * cart.count) from cart JOIN coffee on coffee.coffee_id = cart.coffee_id and cart.count > 0")
fun getSumInCart(): Double fun getSumInCart(): Double
@Query("select cart.count from cart JOIN coffee on coffee.uid = cart.coffee_id where coffee.uid = :coffeeId") @Query("select cart.count from cart JOIN coffee on coffee.coffee_id = cart.coffee_id where coffee.coffee_id = :coffeeId")
fun getCountForCoffee(coffeeId: Int): Double fun getCountForCoffee(coffeeId: Int): Int
@Insert @Insert
suspend fun insert(cart: Cart) suspend fun insert(cart: Cart)

View File

@ -13,7 +13,7 @@ interface CoffeeDao {
@Query("select * from coffee order by name collate nocase asc") @Query("select * from coffee order by name collate nocase asc")
fun getAllCoffees(): PagingSource<Int, Coffee> fun getAllCoffees(): PagingSource<Int, Coffee>
@Query("select coffee.uid, name, cost, ingredients from coffee where coffee.uid = :uid") @Query("select coffee.coffee_id, name, cost, ingredients from coffee where coffee.coffee_id = :uid")
suspend fun getByUid(uid: Int): Coffee? suspend fun getByUid(uid: Int): Coffee?
@Insert @Insert

View File

@ -0,0 +1,56 @@
package com.zyzf.coffeepreorder.database.dao
import androidx.paging.PagingSource
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import kotlinx.coroutines.flow.Flow
@Dao
interface OrderDao {
@Query("select * from `order` order by order_id collate nocase asc")
fun getAll(): PagingSource<Int, Order>
@Query("select * from `order` where `order`.order_id = :uid")
suspend fun getByUid(uid: Int): Order?
@Query("""
SELECT `order`.* FROM `order`
WHERE user_id = :userId
""")
fun getOrdersByUser(userId: Int): PagingSource<Int, Order>
@Query("""
SELECT coffee.* FROM coffee
INNER JOIN ordercoffeecrossref ON coffee.coffee_id = ordercoffeecrossref.coffee_id
WHERE ordercoffeecrossref.order_id = :orderId
""")
fun getCoffeesByOrder(orderId: Int): Flow<List<Coffee>>
@Query("""
SELECT `order`.* FROM `order`
INNER JOIN ordercoffeecrossref ON `order`.order_id = ordercoffeecrossref.order_id
WHERE date >= :startDate and date <= :endDate
""")
fun getOrdersByDate(startDate: String, endDate: String): Flow<List<OrderWithCoffees>>
@Insert
fun insert(order: Order): Long
@Insert
suspend fun insert(vararg order: Order)
@Update
fun update(order: Order): Int
@Delete
suspend fun delete(order: Order)
@Query("delete from `order`")
suspend fun deleteAll()
}

View File

@ -0,0 +1,17 @@
package com.zyzf.coffeepreorder.database.dao
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
@Dao
interface OrderWithCoffees {
@Query("select * from ordercoffeecrossref")
fun getAll(): List<OrderCoffeeCrossRef>
@Insert
suspend fun insert(vararg orderCoffee: OrderCoffeeCrossRef)
@Delete
suspend fun delete(orderCoffee: OrderCoffeeCrossRef)
}

View File

@ -16,7 +16,7 @@ interface UserDao {
@Query("select * from user where login = :login and password = :password") @Query("select * from user where login = :login and password = :password")
suspend fun tryLogin(login: String, password: String): User? suspend fun tryLogin(login: String, password: String): User?
@Query("select * from user where uid = :uid") @Query("select * from user where user_id = :uid")
suspend fun getByUid(uid: Int): User? suspend fun getByUid(uid: Int): User?
@Insert @Insert

View File

@ -4,23 +4,26 @@ import androidx.room.ColumnInfo
import androidx.room.Entity import androidx.room.Entity
import androidx.room.ForeignKey import androidx.room.ForeignKey
import androidx.room.Ignore import androidx.room.Ignore
import androidx.room.Index
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
@Entity(tableName = "cart", foreignKeys = [ //@Entity(tableName = "cart", foreignKeys = [
ForeignKey( // ForeignKey(
entity = Cart::class, // entity = Coffee::class,
parentColumns = ["uid"], // parentColumns = arrayOf("coffee_id"),
childColumns = ["coffee_id"], // childColumns = arrayOf("coffee_id"),
onDelete = ForeignKey.RESTRICT, // onDelete = ForeignKey.SET_DEFAULT,
onUpdate = ForeignKey.RESTRICT // onUpdate = ForeignKey.CASCADE
) // )
]) //])
@Entity(tableName = "cart")
data class Cart( data class Cart(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int = 0, @ColumnInfo(name = "cart_id")
val cartId: Int = 0,
@ColumnInfo(name = "coffee_id", index = true) @ColumnInfo(name = "coffee_id", index = true)
val coffeeId: Int, val coffeeId: Int,
@ColumnInfo(name = "count", index = true) @ColumnInfo(name = "count")
val count: Int = 0 val count: Int = 0
) { ) {
@Ignore @Ignore
@ -33,13 +36,13 @@ data class Cart(
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as Cart other as Cart
if (uid != other.uid) return false if (cartId != other.cartId) return false
if (coffeeId != other.coffeeId) return false if (coffeeId != other.coffeeId) return false
return count == other.count return count == other.count
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = uid var result = cartId
result = 31 * result + coffeeId.hashCode() result = 31 * result + coffeeId.hashCode()
result = 31 * result + count.hashCode() result = 31 * result + count.hashCode()
return result return result

View File

@ -8,7 +8,8 @@ import androidx.room.PrimaryKey
@Entity(tableName = "coffee") @Entity(tableName = "coffee")
data class Coffee( data class Coffee(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int = 0, @ColumnInfo(name = "coffee_id")
val coffeeId: Int = 0,
@ColumnInfo(name = "name") @ColumnInfo(name = "name")
var name: String, var name: String,
@ColumnInfo(name = "cost") @ColumnInfo(name = "cost")
@ -38,14 +39,14 @@ data class Coffee(
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as Coffee other as Coffee
if (uid != other.uid) return false if (coffeeId != other.coffeeId) return false
if (name != other.name) return false if (name != other.name) return false
if (cost != other.cost) return false if (cost != other.cost) return false
return ingredients == other.ingredients return ingredients == other.ingredients
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = uid var result = coffeeId
result = 31 * result + name.hashCode() result = 31 * result + name.hashCode()
result = 31 * result + cost.hashCode() result = 31 * result + cost.hashCode()
result = 31 * result + ingredients.hashCode() result = 31 * result + ingredients.hashCode()

View File

@ -0,0 +1,65 @@
package com.zyzf.coffeepreorder.database.model
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.Ignore
import androidx.room.PrimaryKey
@Entity(
tableName = "order",
foreignKeys = [ForeignKey(
entity = User::class,
parentColumns = arrayOf("user_id"),
childColumns = arrayOf("user_id"),
onDelete = ForeignKey.RESTRICT,
onUpdate = ForeignKey.RESTRICT
)]
)
data class Order(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "order_id")
val orderId: Int = 0,
@ColumnInfo(name = "date")
var date: String,
@ColumnInfo(name = "user_id", index = true)
val userId: Int,
@ColumnInfo(name = "sum")
val sum: Double
) {
@Ignore
constructor(
date: String,
userId: Int,
sum: Double
) : this(0, date, userId, sum)
companion object {
fun getOrder(index: Int = 0): Order {
return Order(
index,
"",
0,
0.0
)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Order
if (orderId != other.orderId) return false
if (date != other.date) return false
if (userId != other.userId) return false
return sum == other.sum
}
override fun hashCode(): Int {
var result = orderId
result = 31 * result + date.hashCode()
result = 31 * result + userId.hashCode()
result = 31 * result + sum.hashCode()
return result
}
}

View File

@ -0,0 +1,14 @@
package com.zyzf.coffeepreorder.database.model
import androidx.room.ColumnInfo
import androidx.room.Entity
@Entity(primaryKeys = ["order_id", "coffee_id"])
class OrderCoffeeCrossRef (
@ColumnInfo(name = "order_id")
val orderId: Int,
@ColumnInfo(name = "coffee_id")
val coffeeId: Int,
@ColumnInfo(name = "count")
val count: Int
)

View File

@ -0,0 +1,27 @@
package com.zyzf.coffeepreorder.database.model
import androidx.room.Embedded
import androidx.room.Junction
import androidx.room.Relation
data class OrderWithCoffees(
@Embedded val order: Order,
@Relation(
parentColumn = "order_id",
entityColumn = "user_id"
)
val user: User,
@Relation(
parentColumn = "order_id",
entityColumn = "coffee_id",
associateBy = Junction(OrderCoffeeCrossRef::class)
)
val coffees: List<Coffee>,
@Relation(
parentColumn = "order_id",
entityColumn = "coffee_id",
entity = OrderCoffeeCrossRef::class,
projection = ["count"]
)
val counts: List<Int>
)

View File

@ -7,7 +7,8 @@ import androidx.room.TypeConverters
enum class RemoteKeyType(private val type: String) { enum class RemoteKeyType(private val type: String) {
COFFEE(Coffee::class.simpleName ?: "Coffee"), COFFEE(Coffee::class.simpleName ?: "Coffee"),
USER(User::class.simpleName ?: "User"); USER(User::class.simpleName ?: "User"),
ORDER(Order::class.simpleName ?: "Order");
@TypeConverter @TypeConverter
fun toRemoteKeyType(value: String) = entries.first { it.type == value } fun toRemoteKeyType(value: String) = entries.first { it.type == value }

View File

@ -8,7 +8,8 @@ import androidx.room.PrimaryKey
@Entity(tableName = "user") @Entity(tableName = "user")
data class User( data class User(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val uid: Int = 0, @ColumnInfo(name = "user_id")
val userId: Int = 0,
@ColumnInfo(name = "login") @ColumnInfo(name = "login")
var login: String, var login: String,
@ColumnInfo(name = "fio") @ColumnInfo(name = "fio")
@ -46,7 +47,7 @@ data class User(
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as User other as User
if (uid != other.uid) return false if (userId != other.userId) return false
if (login != other.login) return false if (login != other.login) return false
if (fio != other.fio) return false if (fio != other.fio) return false
if (phone != other.phone) return false if (phone != other.phone) return false
@ -55,7 +56,7 @@ data class User(
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = uid var result = userId
result = 31 * result + login.hashCode() result = 31 * result + login.hashCode()
result = 31 * result + fio.hashCode() result = 31 * result + fio.hashCode()
result = 31 * result + phone.hashCode() result = 31 * result + phone.hashCode()

View File

@ -9,10 +9,11 @@ interface CartRepository {
suspend fun getAll(): Cart suspend fun getAll(): Cart
suspend fun insert(cart: Cart) suspend fun insert(cart: Cart)
fun getAllInCart(): Flow<PagingData<Coffee>> fun getAllInCart(): Flow<PagingData<Coffee>>
suspend fun getAllInCartList(): List<Coffee>
fun getSumInCart(): Double fun getSumInCart(): Double
suspend fun insertCoffee(coffeeId: Int, count: Int) suspend fun insertCoffee(coffeeId: Int, count: Int)
suspend fun deleteCoffee(coffeeId: Int, count: Int) suspend fun deleteCoffee(coffeeId: Int, count: Int)
fun getCountForCoffee(coffeeId: Int): Double fun getCountForCoffee(coffeeId: Int): Int
suspend fun update(cart: Cart) suspend fun update(cart: Cart)
suspend fun deleteAll() suspend fun deleteAll()
} }

View File

@ -17,12 +17,14 @@ class OfflineCartRepository(private val cartDao: CartDao) : CartRepository {
), ),
pagingSourceFactory = cartDao::getAllInCart pagingSourceFactory = cartDao::getAllInCart
).flow ).flow
override suspend fun getAllInCartList(): List<Coffee> = cartDao.getAllInCartList()
override fun getSumInCart(): Double = cartDao.getSumInCart() override fun getSumInCart(): Double = cartDao.getSumInCart()
override suspend fun getAll(): Cart = cartDao.getAll() override suspend fun getAll(): Cart = cartDao.getAll()
override suspend fun insert(cart: Cart) = cartDao.insert(cart) override suspend fun insert(cart: Cart) = cartDao.insert(cart)
override suspend fun insertCoffee(coffeeId: Int, count: Int) = cartDao.insertCoffee(coffeeId, count) override suspend fun insertCoffee(coffeeId: Int, count: Int) = cartDao.insertCoffee(coffeeId, count)
override suspend fun deleteCoffee(coffeeId: Int, count: Int) = cartDao.deleteCoffee(coffeeId, count) override suspend fun deleteCoffee(coffeeId: Int, count: Int) = cartDao.deleteCoffee(coffeeId, count)
override fun getCountForCoffee(coffeeId: Int): Double = cartDao.getCountForCoffee(coffeeId) override fun getCountForCoffee(coffeeId: Int): Int = cartDao.getCountForCoffee(coffeeId)
override suspend fun update(cart: Cart) = cartDao.update(cart) override suspend fun update(cart: Cart) = cartDao.update(cart)
override suspend fun deleteAll() = cartDao.deleteAll() override suspend fun deleteAll() = cartDao.deleteAll()
} }

View File

@ -0,0 +1,33 @@
package com.zyzf.coffeepreorder.database.repository
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.PagingSource
import com.zyzf.coffeepreorder.database.AppContainer
import com.zyzf.coffeepreorder.database.dao.OrderDao
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import kotlinx.coroutines.flow.Flow
class OfflineOrderRepository(private val orderDao: OrderDao) : OrderRepository {
override fun getOrdersByUser(id: Int): Flow<PagingData<Order>> = Pager(
config = PagingConfig(
pageSize = AppContainer.LIMIT,
enablePlaceholders = false
),
pagingSourceFactory = { orderDao.getOrdersByUser(id) }
).flow
override suspend fun getCoffeesByOrder(orderId: Int): Flow<List<Coffee>> = orderDao.getCoffeesByOrder(orderId)
override suspend fun insert(order: Order) = orderDao.insert(order)
override suspend fun getOrdersByDate(
startDate: String,
endDate: String
): Flow<List<OrderWithCoffees>> = orderDao.getOrdersByDate(startDate, endDate)
fun getOrdersByUserPagingSource(id: Int): PagingSource<Int, Order> = orderDao.getOrdersByUser(id)
suspend fun deleteAll() = orderDao.deleteAll()
suspend fun insertOrders(orders: List<Order>) =
orderDao.insert(*orders.toTypedArray())
}

View File

@ -0,0 +1,12 @@
package com.zyzf.coffeepreorder.database.repository
import com.zyzf.coffeepreorder.database.dao.OrderWithCoffees
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
class OfflineOrderWithCoffeesRepository(private val orderCoffeesDAO: OrderWithCoffees) : OrderWithCoffeesRepository {
override suspend fun getAll() = orderCoffeesDAO.getAll()
override suspend fun insert(orderCoffee: OrderCoffeeCrossRef) = orderCoffeesDAO.insert(orderCoffee)
override suspend fun delete(orderCoffee: OrderCoffeeCrossRef) = orderCoffeesDAO.delete(orderCoffee)
suspend fun insertAll(orderBouquet: List<OrderCoffeeCrossRef>) =
orderCoffeesDAO.insert(*orderBouquet.toTypedArray())
}

View File

@ -0,0 +1,14 @@
package com.zyzf.coffeepreorder.database.repository
import androidx.paging.PagingData
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import kotlinx.coroutines.flow.Flow
interface OrderRepository {
suspend fun getCoffeesByOrder(orderId: Int): Flow<List<Coffee>>
fun getOrdersByUser(id: Int): Flow<PagingData<Order>>
suspend fun insert(order: Order): Long
suspend fun getOrdersByDate(startDate: String, endDate: String): Flow<List<OrderWithCoffees>>
}

View File

@ -0,0 +1,9 @@
package com.zyzf.coffeepreorder.database.repository
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
interface OrderWithCoffeesRepository {
suspend fun getAll(): List<OrderCoffeeCrossRef>
suspend fun insert(orderCoffee: OrderCoffeeCrossRef)
suspend fun delete(orderCoffee: OrderCoffeeCrossRef)
}

View File

@ -8,8 +8,10 @@ import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.ui.cart.CartViewModel import com.zyzf.coffeepreorder.ui.cart.CartViewModel
import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel import com.zyzf.coffeepreorder.ui.coffee.CoffeeListViewModel
import com.zyzf.coffeepreorder.ui.login.LoginViewModel import com.zyzf.coffeepreorder.ui.login.LoginViewModel
import com.zyzf.coffeepreorder.ui.order.OrderViewModel
import com.zyzf.coffeepreorder.ui.profile.ProfileViewModel import com.zyzf.coffeepreorder.ui.profile.ProfileViewModel
import com.zyzf.coffeepreorder.ui.register.RegisterViewModel import com.zyzf.coffeepreorder.ui.register.RegisterViewModel
import com.zyzf.coffeepreorder.ui.statistic.OrderStatisticViewModel
object AppViewModelProvider { object AppViewModelProvider {
val Factory = viewModelFactory { val Factory = viewModelFactory {
@ -26,7 +28,16 @@ object AppViewModelProvider {
RegisterViewModel(coffeeApplication().container.userRestRepository) RegisterViewModel(coffeeApplication().container.userRestRepository)
} }
initializer { initializer {
ProfileViewModel(coffeeApplication().container.userRestRepository) ProfileViewModel(coffeeApplication().container.userRestRepository,
coffeeApplication().container.cartRepository)
}
initializer {
OrderStatisticViewModel(coffeeApplication().container.orderRestRepository)
}
initializer {
OrderViewModel(coffeeApplication().container.orderRestRepository,
coffeeApplication().container.orderCoffeesRestRepository,
coffeeApplication().container.cartRepository)
} }
} }
} }

View File

@ -104,7 +104,7 @@ fun Cart(
) )
CartList( CartList(
coffeeList = coffeeListUiState, coffeeList = coffeeListUiState,
onDeleteFromCartClick = {currentCoffee: Coffee -> onDeleteFromCartClick = { currentCoffee: Coffee ->
coffee.value = currentCoffee coffee.value = currentCoffee
openDialog.value = true openDialog.value = true
} }
@ -126,7 +126,7 @@ fun Cart(
private fun CartList( private fun CartList(
coffeeList: LazyPagingItems<Coffee>, coffeeList: LazyPagingItems<Coffee>,
onDeleteFromCartClick: (coffee: Coffee) -> Unit, onDeleteFromCartClick: (coffee: Coffee) -> Unit,
getCoffeeCount: (coffeeId: Int) -> Double getCoffeeCount: (Int) -> Int
) { ) {
LazyColumn( LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally) { horizontalAlignment = Alignment.CenterHorizontally) {
@ -151,7 +151,7 @@ private fun CartList(
private fun CartListItem ( private fun CartListItem (
coffee: Coffee, coffee: Coffee,
onDeleteFromCartClick: (coffee: Coffee) -> Unit, onDeleteFromCartClick: (coffee: Coffee) -> Unit,
getCoffeeCount: (coffeeId: Int) -> Double getCoffeeCount: (coffeeId: Int) -> Int
) { ) {
Row(modifier = Modifier Row(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -159,7 +159,7 @@ private fun CartListItem (
.padding(bottom = 10.dp, top = 10.dp), .padding(bottom = 10.dp, top = 10.dp),
horizontalArrangement = Arrangement.SpaceAround) { horizontalArrangement = Arrangement.SpaceAround) {
AsyncImage( AsyncImage(
model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.uid +".png") model = ImageRequest.Builder(context = LocalContext.current).data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.coffeeId +".png")
.crossfade(true).build(), .crossfade(true).build(),
error = painterResource(R.drawable.ic_broken_image), error = painterResource(R.drawable.ic_broken_image),
placeholder = painterResource(R.drawable.loading_img), placeholder = painterResource(R.drawable.loading_img),
@ -173,7 +173,7 @@ private fun CartListItem (
Modifier Modifier
.weight(2f) .weight(2f)
.padding(start = 20.dp)) { .padding(start = 20.dp)) {
val coffeeCount: Double = getCoffeeCount(coffee.uid) val coffeeCount: Int = getCoffeeCount(coffee.coffeeId)
val currentCoffeeName: String = if (coffeeCount > 1) { val currentCoffeeName: String = if (coffeeCount > 1) {
coffee.name + " x" + coffeeCount.toString() coffee.name + " x" + coffeeCount.toString()
} else { } else {

View File

@ -11,11 +11,11 @@ class CartViewModel(
) : ViewModel() { ) : ViewModel() {
val coffeeListUiState: Flow<PagingData<Coffee>> = cartRepository.getAllInCart() val coffeeListUiState: Flow<PagingData<Coffee>> = cartRepository.getAllInCart()
fun getCountForCoffee(coffeeId: Int): Double { fun getCountForCoffee(coffeeId: Int): Int {
return cartRepository.getCountForCoffee(coffeeId) return cartRepository.getCountForCoffee(coffeeId)
} }
suspend fun deleteCoffeeFromCart(coffee: Coffee) { suspend fun deleteCoffeeFromCart(coffee: Coffee) {
cartRepository.deleteCoffee(coffee.uid, 1) cartRepository.deleteCoffee(coffee.coffeeId, 1)
} }
} }

View File

@ -180,7 +180,7 @@ fun CoffeeList(
imageUri = imageUri, imageUri = imageUri,
beforeOpen = {coffeeUid: Int -> beforeOpen = {coffeeUid: Int ->
if (coffeeUid != 0 && !isImageChanged.value) { if (coffeeUid != 0 && !isImageChanged.value) {
imageUri = Uri.parse("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.value.uid +".png") imageUri = Uri.parse("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.value.coffeeId +".png")
} }
} }
) )
@ -199,7 +199,7 @@ private fun AddEditModalBottomSheet(
imageUri: Any?, imageUri: Any?,
beforeOpen: (coffeeUid: Int) -> Unit beforeOpen: (coffeeUid: Int) -> Unit
) { ) {
beforeOpen(coffee.value.uid) beforeOpen(coffee.value.coffeeId)
var name: String by remember { mutableStateOf("")} var name: String by remember { mutableStateOf("")}
var cost: Double by remember { mutableDoubleStateOf(0.0) } var cost: Double by remember { mutableDoubleStateOf(0.0) }
var ingredients: String by remember { mutableStateOf("")} var ingredients: String by remember { mutableStateOf("")}
@ -271,16 +271,16 @@ private fun AddEditModalBottomSheet(
.build(), .build(),
) )
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
if (coffee.value.uid == 0) { if (coffee.value.coffeeId == 0) {
Button(onClick = { Button(onClick = {
onAddClick(Coffee(coffee.value.uid, name, cost, ingredients), context) onAddClick(Coffee(coffee.value.coffeeId, name, cost, ingredients), context)
openDialog.value = false openDialog.value = false
}, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) { }, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) {
Text("Добавить") Text("Добавить")
} }
} else { } else {
Button(onClick = { Button(onClick = {
onEditClick(Coffee(coffee.value.uid, name, cost, ingredients), context) onEditClick(Coffee(coffee.value.coffeeId, name, cost, ingredients), context)
openDialog.value = false openDialog.value = false
}, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) { }, modifier = Modifier.padding(0.dp, 10.dp, 0.dp, 30.dp)) {
Text("Изменить") Text("Изменить")
@ -339,7 +339,7 @@ private fun CoffeeListItem(
AsyncImage( AsyncImage(
model = ImageRequest model = ImageRequest
.Builder(context = LocalContext.current) .Builder(context = LocalContext.current)
.data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.uid +".png") .data("https://zyzf.space/s/zXgFRTmbR4KMxMH/download?path=&files=coffee_image_" + coffee.coffeeId +".png")
.crossfade(true).build(), .crossfade(true).build(),
error = painterResource(R.drawable.ic_broken_image), error = painterResource(R.drawable.ic_broken_image),
placeholder = painterResource(R.drawable.loading_img), placeholder = painterResource(R.drawable.loading_img),
@ -363,7 +363,7 @@ private fun CoffeeListItem(
.padding(top = 5.dp)) { .padding(top = 5.dp)) {
Button( Button(
onClick = { onClick = {
onAddToCartClick(coffee.uid) onAddToCartClick(coffee.coffeeId)
}, },
shape = CircleShape, shape = CircleShape,
modifier = Modifier.fillMaxWidth(fraction = 0.75f) modifier = Modifier.fillMaxWidth(fraction = 0.75f)

View File

@ -55,6 +55,7 @@ class CoffeeListViewModel(
suspend fun editCoffee(coffee: Coffee, imageUri: Any?, context: Context) { suspend fun editCoffee(coffee: Coffee, imageUri: Any?, context: Context) {
val editedCoffee: Int = coffeeRepository.update(coffee) val editedCoffee: Int = coffeeRepository.update(coffee)
if (imageUri !is Uri) return if (imageUri !is Uri) return
if (imageUri.host == "zyzf.space") return
val inputStream = context.contentResolver.openInputStream(imageUri) val inputStream = context.contentResolver.openInputStream(imageUri)
val bitmap = BitmapFactory.decodeStream(inputStream) val bitmap = BitmapFactory.decodeStream(inputStream)

View File

@ -1,6 +1,5 @@
package com.zyzf.coffeepreorder.ui.login package com.zyzf.coffeepreorder.ui.login
import android.content.res.Configuration
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@ -16,7 +15,6 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -33,7 +31,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
@ -45,8 +42,6 @@ import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.User import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.ui.AppViewModelProvider import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -143,16 +138,3 @@ fun Login(
} }
} }
} }
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun LoginPreview() {
CoffeePreorderTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
Login(null)
}
}
}

View File

@ -9,7 +9,7 @@ class LoginViewModel(
) : ViewModel() { ) : ViewModel() {
suspend fun tryLogin(login: String, password: String): User? { suspend fun tryLogin(login: String, password: String): User? {
val user: User? = userRepository.tryLogin(login, password) val user: User? = userRepository.tryLogin(login, password)
return if (user?.uid == 0) { return if (user?.userId == 0) {
null null
} else { } else {
user user

View File

@ -1,5 +1,7 @@
package com.zyzf.coffeepreorder.ui.navigation package com.zyzf.coffeepreorder.ui.navigation
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -26,6 +28,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.R import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.ui.cart.Cart import com.zyzf.coffeepreorder.ui.cart.Cart
import com.zyzf.coffeepreorder.ui.coffee.CoffeeList import com.zyzf.coffeepreorder.ui.coffee.CoffeeList
@ -33,6 +36,7 @@ import com.zyzf.coffeepreorder.ui.login.Login
import com.zyzf.coffeepreorder.ui.order.Order import com.zyzf.coffeepreorder.ui.order.Order
import com.zyzf.coffeepreorder.ui.profile.Profile import com.zyzf.coffeepreorder.ui.profile.Profile
import com.zyzf.coffeepreorder.ui.register.Register import com.zyzf.coffeepreorder.ui.register.Register
import com.zyzf.coffeepreorder.ui.statistic.OrderStatistic
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -74,6 +78,11 @@ fun Navbar(
) { ) {
NavigationBar(modifier) { NavigationBar(modifier) {
Screen.bottomBarItems.forEach { screen -> Screen.bottomBarItems.forEach { screen ->
if (CoffeeApplication.currentUser != null &&
CoffeeApplication.currentUser!!.role != "admin" &&
screen.route == "order-statistic") {
return@forEach
}
NavigationBarItem( NavigationBarItem(
icon = { Icon(screen.icon, contentDescription = null) }, icon = { Icon(screen.icon, contentDescription = null) },
label = { Text(stringResource(screen.resourceId)) }, label = { Text(stringResource(screen.resourceId)) },
@ -92,6 +101,7 @@ fun Navbar(
} }
} }
@Composable @Composable
fun Navhost( fun Navhost(
navController: NavHostController, navController: NavHostController,
@ -109,6 +119,8 @@ fun Navhost(
composable(Screen.Profile.route) { Profile(navController) } composable(Screen.Profile.route) { Profile(navController) }
composable(Screen.Cart.route) { Cart(navController) } composable(Screen.Cart.route) { Cart(navController) }
composable(Screen.Order.route) { Order(navController) } composable(Screen.Order.route) { Order(navController) }
composable(Screen.OrderStatistic.route) { OrderStatistic() }
} }
} }

View File

@ -3,11 +3,13 @@ package com.zyzf.coffeepreorder.ui.navigation
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.ShoppingCart import androidx.compose.material.icons.filled.ShoppingCart
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import com.zyzf.coffeepreorder.R import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.database.AppContainer
enum class Screen( enum class Screen(
val route: String, val route: String,
@ -32,13 +34,17 @@ enum class Screen(
), ),
Order( Order(
"order", R.string.coffee_order, Icons.Filled.ShoppingCart "order", R.string.coffee_order, Icons.Filled.ShoppingCart
),
OrderStatistic(
"order-statistic", R.string.coffee_order_statistic, Icons.Filled.DateRange
); );
companion object { companion object {
val bottomBarItems = listOf( val bottomBarItems = listOf(
CoffeeList, CoffeeList,
Profile, Profile,
Cart Cart,
OrderStatistic
) )
fun getItem(route: String): Screen? { fun getItem(route: String): Screen? {

View File

@ -1,6 +1,5 @@
package com.zyzf.coffeepreorder.ui.order package com.zyzf.coffeepreorder.ui.order
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -24,7 +23,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TimePicker import androidx.compose.material3.TimePicker
import androidx.compose.material3.rememberDatePickerState import androidx.compose.material3.rememberDatePickerState
@ -33,18 +31,18 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.zyzf.coffeepreorder.database.AppDatabase import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Calendar import java.util.Calendar
@ -53,18 +51,21 @@ import java.util.Locale
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Order(navController: NavController?) { fun Order(
navController: NavController?,
viewModel: OrderViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val coroutineScope = rememberCoroutineScope()
val openDatePicker = remember { mutableStateOf(false) } val openDatePicker = remember { mutableStateOf(false) }
val openTimePicker = remember { mutableStateOf(false) } val openTimePicker = remember { mutableStateOf(false) }
val calendar = Calendar.getInstance() val calendar = Calendar.getInstance()
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = calendar.timeInMillis) val datePickerState = rememberDatePickerState(initialSelectedDateMillis = calendar.timeInMillis)
val timePickerState = rememberTimePickerState(is24Hour = true, initialHour = calendar.get(Calendar.HOUR_OF_DAY), initialMinute = calendar.get(Calendar.MINUTE)) val timePickerState = rememberTimePickerState(is24Hour = true, initialHour = calendar.get(Calendar.HOUR_OF_DAY), initialMinute = calendar.get(Calendar.MINUTE))
val formatter = SimpleDateFormat("dd.MM.yyyy", Locale.ROOT) val formatter = SimpleDateFormat("dd.MM.yyyy", Locale.ROOT)
val context = LocalContext.current
val sum = remember { mutableStateOf(0.0) } val sum = remember { mutableStateOf(0.0) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
sum.value = AppDatabase.getInstance(context).cartDao().getSumInCart() sum.value = viewModel.getSumInCart()
} }
} }
Column(Modifier.padding(all = 10.dp), horizontalAlignment = Alignment.CenterHorizontally) { Column(Modifier.padding(all = 10.dp), horizontalAlignment = Alignment.CenterHorizontally) {
@ -107,7 +108,12 @@ fun Order(navController: NavController?) {
} }
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
onClick = { navController?.navigate(Screen.CoffeeList.route) }, onClick = {
coroutineScope.launch {
viewModel.createNewOrder(datePickerState.selectedDateMillis)
}
navController?.navigate(Screen.Cart.route)
},
icon = { Icon(Icons.Filled.Add, "Оформить заказ") }, icon = { Icon(Icons.Filled.Add, "Оформить заказ") },
text = { Text(text = "Оформить заказ") }, text = { Text(text = "Оформить заказ") },
modifier = Modifier modifier = Modifier
@ -170,16 +176,3 @@ fun Order(navController: NavController?) {
} }
} }
} }
@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun OrderPreview() {
CoffeePreorderTheme {
Surface(
color = MaterialTheme.colorScheme.background
) {
Order(null)
}
}
}

View File

@ -0,0 +1,36 @@
package com.zyzf.coffeepreorder.ui.order
import androidx.lifecycle.ViewModel
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.database.model.Coffee
import com.zyzf.coffeepreorder.database.model.Order
import com.zyzf.coffeepreorder.database.model.OrderCoffeeCrossRef
import com.zyzf.coffeepreorder.database.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.OrderRepository
import com.zyzf.coffeepreorder.database.repository.OrderWithCoffeesRepository
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
class OrderViewModel(
private val orderRepository: OrderRepository,
private val orderWithCoffeesRepository: OrderWithCoffeesRepository,
private val cartRepository: CartRepository
) : ViewModel() {
fun getSumInCart(): Double {
return cartRepository.getSumInCart()
}
suspend fun createNewOrder(date: Long?) {
val coffees: List<Coffee> = cartRepository.getAllInCartList()
if (coffees.isEmpty() || date == null) {
return
}
val cartSum: Double = cartRepository.getSumInCart()
val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.ROOT)
val currentOrderId: Long = orderRepository.insert(Order(formatter.format(Date(date)), CoffeeApplication.currentUser!!.userId, cartSum))
coffees.forEach { coffee: Coffee ->
orderWithCoffeesRepository.insert(OrderCoffeeCrossRef(currentOrderId.toInt(), coffee.coffeeId, cartRepository.getCountForCoffee(coffee.coffeeId)))
}
}
}

View File

@ -47,10 +47,8 @@ import com.zyzf.coffeepreorder.R
import com.zyzf.coffeepreorder.database.AppDatabase import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.User import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.ui.AppViewModelProvider import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.login.LoginViewModel
import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -204,7 +202,7 @@ fun Profile(
onClick = { onClick = {
coroutineScope.launch { coroutineScope.launch {
val user: User = viewModel.changeUser(user.password, userOldPsswd.value, userNewPsswd.value, userNewPsswd.value, val user: User = viewModel.changeUser(user.password, userOldPsswd.value, userNewPsswd.value, userNewPsswd.value,
user.uid, userLogin, userFIO, userPhone, user.role) user.userId, userLogin, userFIO, userPhone, user.role)
CoffeeApplication.currentUser = user CoffeeApplication.currentUser = user
} }
openDialogEdit.value = false openDialogEdit.value = false
@ -241,8 +239,8 @@ fun Profile(
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
GlobalScope.launch (Dispatchers.Main) { coroutineScope.launch {
CoffeeApplication.currentUser = null viewModel.logout()
} }
openDialogExit.value = false openDialogExit.value = false
navController?.navigate(Screen.Login.route) navController?.navigate(Screen.Login.route)
@ -279,9 +277,8 @@ fun Profile(
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = {
GlobalScope.launch (Dispatchers.Main) { coroutineScope.launch {
CoffeeApplication.currentUser = null viewModel.deleteAccount(user)
AppDatabase.getInstance(context).userDao().delete(user)
} }
openDialogDelete.value = false openDialogDelete.value = false
navController?.navigate(Screen.Login.route) navController?.navigate(Screen.Login.route)

View File

@ -2,12 +2,13 @@ package com.zyzf.coffeepreorder.ui.profile
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import com.zyzf.coffeepreorder.CoffeeApplication import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.database.AppDatabase
import com.zyzf.coffeepreorder.database.model.User import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.database.repository.CartRepository
import com.zyzf.coffeepreorder.database.repository.UserRepository import com.zyzf.coffeepreorder.database.repository.UserRepository
class ProfileViewModel( class ProfileViewModel(
private val userRepository: UserRepository private val userRepository: UserRepository,
private val cartRepository: CartRepository
) : ViewModel() { ) : ViewModel() {
suspend fun changeUser(currentUserPassw: String, suspend fun changeUser(currentUserPassw: String,
userOldPsswd: String, userOldPsswd: String,
@ -25,6 +26,16 @@ class ProfileViewModel(
val userUid: Int? = userRepository.update(User( val userUid: Int? = userRepository.update(User(
userUid, userLogin, userFIO, userPhone, currentUserPassw, userRole)) userUid, userLogin, userFIO, userPhone, currentUserPassw, userRole))
} }
return userRepository.getByUid(userUid!!)!! return userRepository.getByUid(userUid)!!
}
suspend fun logout() {
CoffeeApplication.currentUser = null
cartRepository.deleteAll()
}
suspend fun deleteAccount(user: User) {
logout()
userRepository.delete(user)
} }
} }

View File

@ -44,7 +44,6 @@ import com.zyzf.coffeepreorder.database.model.User
import com.zyzf.coffeepreorder.ui.AppViewModelProvider import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import com.zyzf.coffeepreorder.ui.navigation.Screen import com.zyzf.coffeepreorder.ui.navigation.Screen
import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme import com.zyzf.coffeepreorder.ui.theme.CoffeePreorderTheme
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@ -0,0 +1,281 @@
package com.zyzf.coffeepreorder.ui.statistic
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.DatePicker
import androidx.compose.material3.DatePickerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.lifecycle.viewmodel.compose.viewModel
import com.zyzf.coffeepreorder.CoffeeApplication
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import com.zyzf.coffeepreorder.ui.AppViewModelProvider
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Calendar
import java.util.Date
import java.util.Locale
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OrderStatistic(
viewModel: OrderStatisticViewModel = viewModel(factory = AppViewModelProvider.Factory)
) {
val openDatePicker = remember { mutableStateOf(false) }
val calendar = Calendar.getInstance()
val datePickerState = rememberDatePickerState(initialSelectedDateMillis = calendar.timeInMillis)
val coroutineScope = rememberCoroutineScope()
val orderListUiState = viewModel.orderListUiState.collectAsState(initial = listOf())
val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("dd-MM-yyyy")
val startDate = remember { mutableStateOf(LocalDate.now().minusDays(1).format(formatter))}
val endDate = remember { mutableStateOf(LocalDate.now().format(formatter))}
val isStartDate = remember { mutableStateOf(true)}
Scaffold(
topBar = {},
floatingActionButton = {
if (CoffeeApplication.currentUser?.role == "admin") {
FloatingActionButton(
onClick = {
coroutineScope.launch {
viewModel.getOrdersByDate(startDate.value, endDate.value, coroutineScope)
}
},
Modifier
.padding(all = 20.dp)
) {
Icon(
imageVector = Icons.Filled.Check,
contentDescription = "Perform",
modifier = Modifier.size(20.dp)
)
}
}
}
) { innerPadding ->
Column (modifier = Modifier
.padding(innerPadding).fillMaxSize()) {
StartEndDateBlock(
startDate = startDate,
endDate = endDate,
onStartDateClick = {
isStartDate.value = true
openDatePicker.value = true
},
onEndDateClick = {
isStartDate.value = false
openDatePicker.value = true
}
)
TotalBlock(orderList = orderListUiState.value)
OrderList(
orderList = orderListUiState
)
}
DateDialog(
startDate = startDate,
endDate = endDate,
isStartDate = isStartDate,
openDatePicker = openDatePicker,
datePickerState = datePickerState
)
}
}
@Composable
private fun OrderList(
orderList: State<List<OrderWithCoffees>>,
) {
LazyColumn(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
items(items = orderList.value) {order ->
OrderListItem(
order = order
)
}
}
}
@Composable
private fun StartEndDateBlock(
startDate: MutableState<String>,
endDate: MutableState<String>,
onStartDateClick: () -> Unit,
onEndDateClick: () -> Unit
) {
Column(
Modifier
.fillMaxWidth()
.heightIn(max = 140.dp)
.padding(bottom = 10.dp, top = 10.dp)) {
Row(horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically) {
Button(onClick = {onStartDateClick()},
shape = CircleShape,
modifier = Modifier.width(100.dp).padding(10.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.primary
)
) {
Text(text = "От")
}
Text(text = startDate.value, modifier = Modifier.padding(10.dp))
Button(onClick = {onEndDateClick()},
shape = CircleShape,
modifier = Modifier.width(100.dp).padding(10.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.primary
)
) {
Text(text = "До")
}
Text(text = endDate.value, modifier = Modifier.padding(10.dp))
}
}
}
@Composable
private fun TotalBlock(
orderList: List<OrderWithCoffees>
) {
Column(
Modifier
.fillMaxWidth()
.heightIn(max = 140.dp)
.padding(bottom = 10.dp, top = 10.dp)) {
Row(horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()) {
Row(modifier = Modifier.width(145.dp)) {
Text(text = "Итого: ", modifier = Modifier.padding(5.dp))
Text(text = orderList.sumOf { it.order.sum }.toString(), modifier = Modifier.padding(5.dp))
}
Row(modifier = Modifier.width(145.dp)) {
Text(text = "Количество: ", modifier = Modifier.padding(5.dp))
Text(text = orderList.count().toString(), modifier = Modifier.padding(5.dp))
}
Row(modifier = Modifier.width(145.dp)) {
Text(text = "Среднее: ", modifier = Modifier.padding(5.dp))
Text(text = (orderList.sumOf { it.order.sum } / orderList.count()).toString(), modifier = Modifier.padding(5.dp))
}
}
}
}
@Composable
private fun OrderListItem(
order: OrderWithCoffees
) {
Column(
Modifier
.fillMaxWidth()
.heightIn(max = 150.dp)
.padding(bottom = 10.dp, top = 10.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = order.user.fio, fontSize = 25.sp)
Text(text = order.user.phone, fontSize = 25.sp)
Text(text = String.format("%.2f", order.order.sum), fontSize = 30.sp)
Row() {
for (i in (0..order.coffees.count()-1)) {
if (order.counts[i] > 1) {
Text(text = order.coffees[i].name + " x" + order.counts[i] + "(" + order.coffees[i].cost * order.counts[i] + ")", fontSize = 15.sp)
} else {
Text(text = order.coffees[i].name + "(" + order.coffees[i].cost + ")", fontSize = 15.sp)
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DateDialog(
startDate: MutableState<String>,
endDate: MutableState<String>,
isStartDate: MutableState<Boolean>,
openDatePicker: MutableState<Boolean>,
datePickerState: DatePickerState
) {
if (openDatePicker.value) {
Dialog(onDismissRequest = { openDatePicker.value = false },
properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = true, usePlatformDefaultWidth = false)
) {
Card(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.7f)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.End) {
Button(
onClick = {
if (datePickerState.selectedDateMillis != null) {
val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.ROOT)
if (isStartDate.value) {
startDate.value = formatter.format(Date(datePickerState.selectedDateMillis!!))
} else {
endDate.value = formatter.format(Date(datePickerState.selectedDateMillis!!))
}
}
openDatePicker.value = false
},
modifier = Modifier.padding(10.dp),
) {
Text("Ок")
}
DatePicker(
state = datePickerState,
modifier = Modifier
.fillMaxSize()
.padding(10.dp)
)
}
}
}
}
}

View File

@ -0,0 +1,41 @@
package com.zyzf.coffeepreorder.ui.statistic
import androidx.lifecycle.ViewModel
import com.zyzf.coffeepreorder.database.model.OrderWithCoffees
import com.zyzf.coffeepreorder.database.repository.OrderRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.time.LocalDate
import java.time.format.DateTimeFormatter
class OrderStatisticViewModel(
private val orderRepository: OrderRepository
) : ViewModel() {
private val formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy")
private var startDate: String = LocalDate.now().minusDays(1).format(formatter)
private var endDate: String = LocalDate.now().format(formatter)
var orderListUiState: Flow<List<OrderWithCoffees>> = temp()
private fun temp(): Flow<List<OrderWithCoffees>> {
var orderWithCoffees: Flow<List<OrderWithCoffees>>? = null
runBlocking {
orderWithCoffees = orderRepository.getOrdersByDate(startDate, endDate)
}
return orderWithCoffees!!
}
fun getOrdersByDate(startDate: String, endDate: String, scope: CoroutineScope) {
this.startDate = startDate
this.endDate = endDate
scope.launch {
orderListUiState = orderRepository.getOrdersByDate(startDate, endDate)
}
}
}

View File

@ -5,6 +5,7 @@
<string name="coffee_main_title">Меню</string> <string name="coffee_main_title">Меню</string>
<string name="coffee_cart">Корзина</string> <string name="coffee_cart">Корзина</string>
<string name="coffee_order">Заказ</string> <string name="coffee_order">Заказ</string>
<string name="coffee_order_statistic">Статистика</string>
<string name="coffee_name">Название</string> <string name="coffee_name">Название</string>
<string name="coffee_cost">Стоимость</string> <string name="coffee_cost">Стоимость</string>
<string name="coffee_ingredients">Ингредиенты</string> <string name="coffee_ingredients">Ингредиенты</string>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<network-security-config> <network-security-config>
<domain-config cleartextTrafficPermitted="true"> <domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.0.100</domain> <domain includeSubdomains="true">192.168.1.230</domain>
</domain-config> </domain-config>
</network-security-config> </network-security-config>

4
backend/Dockerfile Normal file
View File

@ -0,0 +1,4 @@
FROM eclipse-temurin:17-jdk-alpine
VOLUME /tmp
COPY build/libs/yan-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

View File

@ -1,6 +1,6 @@
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.2.0' id 'org.springframework.boot' version '3.2.1'
id 'io.spring.dependency-management' version '1.1.4' id 'io.spring.dependency-management' version '1.1.4'
} }
@ -16,16 +16,15 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-devtools' implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2' //implementation 'com.h2database:h2:2.1.212'
implementation 'org.hibernate.validator:hibernate-validator' implementation 'org.hibernate.validator:hibernate-validator'
implementation 'org.projectlombok:lombok' implementation 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
runtimeOnly 'org.postgresql:postgresql'
} }
tasks.named('test') { tasks.named('test') {
useJUnitPlatform() useJUnitPlatform()
} }
java.targetCompatibility = JavaVersion.VERSION_17

View File

@ -1,7 +1,20 @@
package com.kalyshev.yan.coffee.repository; package com.kalyshev.yan.coffee.repository;
import com.kalyshev.yan.coffee.model.Coffee; import com.kalyshev.yan.coffee.model.Coffee;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.data.util.Pair;
import java.util.List;
public interface CoffeeRepository extends JpaRepository<Coffee, Long> { public interface CoffeeRepository extends JpaRepository<Coffee, Long> {
@Query(value = """
select c, ord.count from Coffee c
join OrderCoffeeCrossRef ord on c.id = ord.coffee.id
join Order o on o.id = ord.order.id
where o.id = :order_id
""")
public List<Object[]> findCoffeesByOrder(@Param("order_id") Long orderId);
} }

View File

@ -0,0 +1,42 @@
package com.kalyshev.yan.order.controller;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
public class OrderCoffeeCrossRefDto {
private Long id;
private Long orderId;
private Long coffeeId;
private int count;
public OrderCoffeeCrossRefDto(){}
public OrderCoffeeCrossRefDto(OrderCoffeeCrossRef orderCoffeeCrossRef) {
this.id = orderCoffeeCrossRef.getId();
this.orderId = orderCoffeeCrossRef.getOrder().getId();
this.coffeeId = orderCoffeeCrossRef.getCoffee().getId();
this.count = orderCoffeeCrossRef.getCount();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getOrderId() {
return orderId;
}
public void setOrderId(Long orderId) {
this.orderId = orderId;
}
public Long getCoffeeId() {
return coffeeId;
}
public void setCoffeeId(Long coffeeId) {
this.coffeeId = coffeeId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}

View File

@ -0,0 +1,70 @@
package com.kalyshev.yan.order.controller;
import com.kalyshev.yan.WebConfiguration;
import com.kalyshev.yan.coffee.controller.CoffeeDto;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
import com.kalyshev.yan.order.model.OrderCoffees;
import com.kalyshev.yan.order.service.OrderService;
import jakarta.validation.Valid;
import org.springframework.data.util.Pair;
import org.springframework.web.bind.annotation.*;
import java.text.ParseException;
import java.util.List;
import java.util.Objects;
@RestController
@RequestMapping(WebConfiguration.REST_API + "/order")
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
@GetMapping("/coffeesByOrder")
public List<Pair<CoffeeDto, Integer>> getCoffeesByOrder(
@RequestParam(value = "orderId") Long orderId
){
return orderService.findCoffeesByOrder(orderId);
}
@GetMapping("/byDate")
public List<OrderCoffees> getOrdersByDate(
@RequestParam(value = "startDate") String startDate,
@RequestParam(value = "endDate") String endDate
) throws ParseException {
return orderService.findOrdersByDate(startDate, endDate);
}
@GetMapping("/byUser")
public List<OrderDto> getOrdersByUser(
@RequestParam(value = "userId") Long userId,
@RequestParam(value = "pageNo", defaultValue = "0", required = false) int pageNo,
@RequestParam(value = "pageSize", defaultValue = "10", required = false) int pageSize,
@RequestParam(value = "sortBy", defaultValue = "id", required = false) String sortBy,
@RequestParam(value = "sortDir", defaultValue = "asc", required = false) String sortDir
) {
return orderService.findOrderByUser(userId, pageNo, pageSize, sortBy, sortDir).stream()
.map(OrderDto::new)
.toList();
}
@GetMapping("/coffeeCrossRef")
public List<OrderCoffeeCrossRef> getOrdersByDate() {
return orderService.findOrderCoffeeCrossRef();
}
@PostMapping("/")
public OrderDto createOrder(@RequestBody @Valid OrderDto orderDto) throws ParseException {
return new OrderDto(orderService.addOrder(orderDto.getDate(), orderDto.getSum(), orderDto.getUser().getId()));
}
@PostMapping("/coffeeCrossRef")
public void createOrderCoffeeCrossRef(@RequestBody @Valid OrderCoffeeCrossRefDto orderCoffeeCrossRefDto) {
orderService.createOrderCoffeeCrossRef(orderCoffeeCrossRefDto.getOrderId(), orderCoffeeCrossRefDto.getCoffeeId(), orderCoffeeCrossRefDto.getCount());
}
@DeleteMapping("/coffeeCrossRef/{id}")
public void deleteOrderCoffeeCrossRef(@PathVariable Long id) {
orderService.deleteOrderCoffeeCrossRef(id);
}
}

View File

@ -0,0 +1,47 @@
package com.kalyshev.yan.order.controller;
import com.kalyshev.yan.order.model.Order;
import com.kalyshev.yan.user.model.User;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
public class OrderDto {
private Long id;
private String date;
private Double sum;
private User user;
public OrderDto(){}
public OrderDto(Order order) {
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy");
this.id = order.getId();
this.date = format.format(order.getDate());
this.sum = order.getSum();
this.user = order.getUser();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public Double getSum() {
return sum;
}
public void setSum(Double sum) {
this.sum = sum;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}

View File

@ -0,0 +1,54 @@
package com.kalyshev.yan.order.model;
import com.kalyshev.yan.user.model.User;
import jakarta.persistence.*;
import java.util.Date;
@Entity
@Table(name = "order_o")
public class Order {
@Id
@GeneratedValue
private Long id;
@Temporal(TemporalType.DATE)
private Date date;
private Double sum;
@ManyToOne(cascade = {CascadeType.MERGE})
@JoinColumn(name = "user_id", unique = false)
private User user;
public Order(Date date, Double sum, User user) {
this.date = date;
this.sum = sum;
this.user = user;
}
public Order(Long id, Date date, Double sum, User user) {
this.id = id;
this.date = date;
this.sum = sum;
this.user = user;
}
public Order() {}
public Long getId() {
return id;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Double getSum() {
return sum;
}
public void setSum(Double sum) {
this.sum = sum;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}

View File

@ -0,0 +1,56 @@
package com.kalyshev.yan.order.model;
import com.kalyshev.yan.coffee.model.Coffee;
import jakarta.persistence.*;
import javax.crypto.Cipher;
@Entity
@Table(name = "order_coffee")
public class OrderCoffeeCrossRef {
@Id
@GeneratedValue
private Long id;
@ManyToOne(cascade = {CascadeType.MERGE})
@JoinColumn(name = "order_id", unique = false)
private Order order;
@ManyToOne(cascade = {CascadeType.MERGE})
@JoinColumn(name = "coffee_id", unique = false)
private Coffee coffee;
@Column(name = "count")
private int count;
public OrderCoffeeCrossRef(Order order, Coffee coffee, int count) {
this.order = order;
this.coffee = coffee;
this.count = count;
}
public OrderCoffeeCrossRef(Long id, Order order, Coffee coffee, int count) {
this.id = id;
this.order = order;
this.coffee = coffee;
this.count = count;
}
public OrderCoffeeCrossRef() {}
public Long getId() {
return id;
}
public Order getOrder() {
return order;
}
public void setOrder(Order order) {
this.order = order;
}
public Coffee getCoffee() {
return coffee;
}
public void setCoffee(Coffee coffee) {
this.coffee = coffee;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}

View File

@ -0,0 +1,33 @@
package com.kalyshev.yan.order.model;
import com.kalyshev.yan.coffee.controller.CoffeeDto;
import com.kalyshev.yan.coffee.model.Coffee;
import com.kalyshev.yan.order.controller.OrderDto;
import org.springframework.data.util.Pair;
import java.util.List;
public class OrderCoffees {
private OrderDto order;
private List<Pair<CoffeeDto, Integer>> coffees;
public OrderCoffees() {
}
public OrderCoffees(OrderDto order, List<Pair<CoffeeDto, Integer>> coffees) {
this.order = order;
this.coffees = coffees;
}
public OrderDto getOrder() {
return order;
}
public void setOrder(OrderDto order) {
this.order = order;
}
public List<Pair<CoffeeDto, Integer>> getCoffees() {
return coffees;
}
public void setCoffees(List<Pair<CoffeeDto, Integer>> coffees) {
this.coffees = coffees;
}
}

View File

@ -0,0 +1,7 @@
package com.kalyshev.yan.order.repository;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderCoffeeCrossRefRepository extends JpaRepository<OrderCoffeeCrossRef, Long> {
}

View File

@ -0,0 +1,7 @@
package com.kalyshev.yan.order.repository;
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException(Long id) {
super(String.format("Order with id [%s] is not found", id));
}
}

View File

@ -0,0 +1,30 @@
package com.kalyshev.yan.order.repository;
import com.kalyshev.yan.coffee.model.Coffee;
import com.kalyshev.yan.order.model.Order;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.Date;
import java.util.List;
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query(value = """
select * from order_o
where order_o.date >= :start_date and order_o.date <= :end_date
""", nativeQuery = true)
public List<Order> findOrdersByDate(@Param("start_date") Date startDate,
@Param("end_date") Date endDate);
@Query(value = """
select new OrderCoffeeCrossRef(ord.id, ord.order, ord.coffee, ord.count) from OrderCoffeeCrossRef ord
""")
public List<OrderCoffeeCrossRef> findOrderCoffeeCrossRef();
public Page<Order> findOrdersByUser(Long userId, Pageable pageable);
}

View File

@ -0,0 +1,130 @@
package com.kalyshev.yan.order.service;
import com.kalyshev.yan.coffee.controller.CoffeeDto;
import com.kalyshev.yan.coffee.model.Coffee;
import com.kalyshev.yan.coffee.repository.CoffeeNotFoundException;
import com.kalyshev.yan.coffee.repository.CoffeeRepository;
import com.kalyshev.yan.order.controller.OrderCoffeeCrossRefDto;
import com.kalyshev.yan.order.controller.OrderDto;
import com.kalyshev.yan.order.model.Order;
import com.kalyshev.yan.order.model.OrderCoffeeCrossRef;
import com.kalyshev.yan.order.model.OrderCoffees;
import com.kalyshev.yan.order.repository.OrderCoffeeCrossRefRepository;
import com.kalyshev.yan.order.repository.OrderNotFoundException;
import com.kalyshev.yan.order.repository.OrderRepository;
import com.kalyshev.yan.user.model.User;
import com.kalyshev.yan.user.repository.UserNotFoundException;
import com.kalyshev.yan.user.repository.UserRepository;
import com.kalyshev.yan.util.validation.ValidatorUtil;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final OrderCoffeeCrossRefRepository orderCoffeeCrossRefRepository;
private final UserRepository userRepository;
private final CoffeeRepository coffeeRepository;
private final ValidatorUtil validatorUtil;
public OrderService(OrderRepository orderRepository,
OrderCoffeeCrossRefRepository orderCoffeeCrossRefRepository,
UserRepository userRepository,
CoffeeRepository coffeeRepository,
ValidatorUtil validatorUtil) {
this.orderRepository = orderRepository;
this.orderCoffeeCrossRefRepository = orderCoffeeCrossRefRepository;
this.userRepository = userRepository;
this.coffeeRepository = coffeeRepository;
this.validatorUtil = validatorUtil;
}
@Transactional
public Order addOrder(String date, Double sum, Long userId) throws ParseException {
if (!StringUtils.hasText(date)) {
throw new IllegalArgumentException("Date is null or empty");
}
if (sum <= 0) {
throw new IllegalArgumentException("Sum is null or negative");
}
if (userId <= 0) {
throw new IllegalArgumentException("UserId is null or negative");
}
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy");
final User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException(userId));
final Order order = new Order(format.parse(date), sum, user);
validatorUtil.validate(order);
return orderRepository.save(order);
}
@Transactional(readOnly = true)
public List<Pair<CoffeeDto, Integer>> findCoffeesByOrder(Long orderId) {
List<Object[]> coffees = coffeeRepository.findCoffeesByOrder(orderId);
List<Pair<CoffeeDto, Integer>> resultCoffees = new ArrayList<>();
for (Object[] coffee : coffees) {
resultCoffees.add(Pair.of(new CoffeeDto((Coffee) coffee[0]),(Integer) coffee[1]));
}
return resultCoffees;
}
@Transactional(readOnly = true)
public List<OrderCoffees> findOrdersByDate(String startDate, String endDate) throws ParseException {
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy", Locale.ROOT);
SimpleDateFormat formatter1 = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT);
Date startDateDate = formatter.parse(startDate);
Date endDateDate = formatter.parse(endDate);
List<Order> orders = orderRepository.findOrdersByDate(startDateDate, endDateDate);
List<OrderCoffees> orderCoffees = new ArrayList<OrderCoffees>();
for (Order order : orders) {
List<Object[]> coffees = coffeeRepository.findCoffeesByOrder(order.getId());
List<Pair<CoffeeDto, Integer>> resultCoffees = new ArrayList<>();
for (Object[] coffee : coffees) {
resultCoffees.add(Pair.of(new CoffeeDto((Coffee) coffee[0]),(Integer) coffee[1]));
}
orderCoffees.add(new OrderCoffees(new OrderDto(order), resultCoffees));
}
return orderCoffees;
}
@Transactional(readOnly = true)
public List<OrderCoffeeCrossRef> findOrderCoffeeCrossRef() {
List<OrderCoffeeCrossRef> orderCoffeeCrossRefs = orderRepository.findOrderCoffeeCrossRef();
return orderCoffeeCrossRefs;
}
@Transactional
public void createOrderCoffeeCrossRef(Long orderId, Long coffeeId, int count) {
final Order order = orderRepository.findById(orderId).orElseThrow(() -> new OrderNotFoundException(orderId));
final Coffee coffee = coffeeRepository.findById(coffeeId).orElseThrow(() -> new CoffeeNotFoundException(coffeeId));
final OrderCoffeeCrossRef orderCoffeeCrossRef = new OrderCoffeeCrossRef(order, coffee, count);
validatorUtil.validate(orderCoffeeCrossRef);
orderCoffeeCrossRefRepository.save(orderCoffeeCrossRef);
}
@Transactional(readOnly = true)
public List<Order> findOrderByUser(Long userId, int pageNo, int pageSize, String sortBy, String sortDir) {
Sort sort = sortDir.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortBy).ascending()
: Sort.by(sortBy).descending();
// create Pageable instance
Pageable pageable = PageRequest.of(pageNo, pageSize, sort);
Page<Order> orders = orderRepository.findOrdersByUser(userId, pageable);
// get content for page object
List<Order> listOfOrders = orders.getContent();
return listOfOrders;
}
@Transactional
public void deleteOrderCoffeeCrossRef(Long id) {
orderCoffeeCrossRefRepository.deleteById(id);
}
}

View File

@ -8,7 +8,7 @@ import org.springframework.data.repository.query.Param;
import java.util.Optional; import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> { public interface UserRepository extends JpaRepository<User, Long> {
@Query(value = "select * from \"USER_U\" where \"LOGIN\" = :login and \"PASSWORD\" = :password", nativeQuery = true) @Query(value = "select * from user_u where login = :login and password = :password", nativeQuery = true)
public Optional<User> tryLogin(@Param("login") String login, public Optional<User> tryLogin(@Param("login") String login,
@Param("password") String password); @Param("password") String password);

View File

@ -1,6 +1,7 @@
package com.kalyshev.yan.util.error; package com.kalyshev.yan.util.error;
import com.kalyshev.yan.coffee.repository.CoffeeNotFoundException; import com.kalyshev.yan.coffee.repository.CoffeeNotFoundException;
import com.kalyshev.yan.order.repository.OrderNotFoundException;
import com.kalyshev.yan.user.repository.UserNotFoundException; import com.kalyshev.yan.user.repository.UserNotFoundException;
import com.kalyshev.yan.util.validation.ValidationException; import com.kalyshev.yan.util.validation.ValidationException;
import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.context.support.DefaultMessageSourceResolvable;
@ -18,7 +19,8 @@ public class AdviceController {
@ExceptionHandler({ @ExceptionHandler({
CoffeeNotFoundException.class, CoffeeNotFoundException.class,
UserNotFoundException.class, UserNotFoundException.class,
ValidationException.class ValidationException.class,
OrderNotFoundException.class
}) })
public ResponseEntity<Object> handleException(Throwable e) { public ResponseEntity<Object> handleException(Throwable e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);

View File

@ -1,11 +1,7 @@
spring.main.banner-mode=off
#server.port=8080
spring.datasource.url=jdbc:h2:file:./data
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.ddl-auto=update
spring.h2.console.enabled=true spring.sql.init.mode=always
spring.h2.console.settings.trace=false spring.sql.init.platform=postgres
spring.h2.console.settings.web-allow-others=true spring.datasource.url=jdbc:postgresql://109.197.199.134:5432/coffee
spring.datasource.username=postgres
spring.datasource.password=gAiCyfGGv5TywE
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true