Initial mobile app implementation
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.gradle/
|
||||||
|
.kotlin/
|
||||||
|
.idea/
|
||||||
|
build/
|
||||||
|
app/build/
|
||||||
|
local.properties
|
||||||
|
*.iml
|
||||||
|
.DS_Store
|
||||||
98
CODEX_HANDOFF.md
Normal file
98
CODEX_HANDOFF.md
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# Codex Handoff
|
||||||
|
|
||||||
|
## Project
|
||||||
|
- Native Android app
|
||||||
|
- Package: `id.abelbirdnest.mobile`
|
||||||
|
- App name: `Abelbirdnest Stock`
|
||||||
|
- Stack: Kotlin + Jetpack Compose
|
||||||
|
- Blueprint source of truth: [mobile-api-blueprint.md](./mobile-api-blueprint.md)
|
||||||
|
|
||||||
|
## API / Environment
|
||||||
|
- Production base URL is used from Postman env.
|
||||||
|
- Active API host used during testing: `https://abelbirdnest.id/api/v1`
|
||||||
|
|
||||||
|
## Test Accounts
|
||||||
|
- `OWNER` -> `abel@zappcare.id` / `abel1234`
|
||||||
|
- `PURCHASING` -> `tini@zappcare.id` / `tini1234`
|
||||||
|
- `SALES` -> `budi@zappcare.id` / `budi1234`
|
||||||
|
- `QC` -> `edi@zappcare.id` / `edi12345`
|
||||||
|
- `WAREHOUSE` -> `jhon@zappcare.id` / `jhon1234`
|
||||||
|
|
||||||
|
## Implemented Modules
|
||||||
|
- `dashboard`
|
||||||
|
- `lots`
|
||||||
|
- `washing`
|
||||||
|
- `stock_adjustments`
|
||||||
|
- `purchases`
|
||||||
|
- `purchase_analyses`
|
||||||
|
- `purchase_realizations`
|
||||||
|
- `fund_requests`
|
||||||
|
- `sales_regular`
|
||||||
|
- `sales_jit`
|
||||||
|
- `consignments`
|
||||||
|
- `lot_transformations`
|
||||||
|
|
||||||
|
## Hidden / Removed From Mobile Navigation
|
||||||
|
- `receipts`
|
||||||
|
- intentionally hidden because newer server/mobile flow no longer uses it for active role navigation
|
||||||
|
|
||||||
|
## Important Behavior
|
||||||
|
- App is server-driven from `mobile/bootstrap`, but module ordering and UI prioritization were aligned to the blueprint where needed.
|
||||||
|
- `PURCHASING` login issue was fixed by not forcing `/mobile/lots` fetch for roles that do not have `lots`.
|
||||||
|
- Quick actions now only show modules that are not already present in the bottom bar.
|
||||||
|
- For `OWNER`, quick actions were reduced to match actual mobile need. Extra actions like `dana`, `sales`, `jit`, `titip jual`, and `washing` were removed from quick actions.
|
||||||
|
|
||||||
|
## Recent UI Changes
|
||||||
|
|
||||||
|
### Login
|
||||||
|
- Brand text changed to `Abelbirdnest`
|
||||||
|
- Centered vertically
|
||||||
|
- `Lupa Kata Sandi?` restored
|
||||||
|
- Removed extra elements below the main login CTA except forgot password
|
||||||
|
|
||||||
|
### Lots
|
||||||
|
- Lot detail top gap was reduced by:
|
||||||
|
- removing extra spacer
|
||||||
|
- disabling nested scaffold content insets in detail
|
||||||
|
- Lot detail footer actions were removed entirely:
|
||||||
|
- `Print Ulang`
|
||||||
|
- `Pindah Lokasi`
|
||||||
|
- `Ubah Status`
|
||||||
|
- reason: no real mobile action flow yet, avoid dead buttons
|
||||||
|
|
||||||
|
### Scan Lot
|
||||||
|
- Scanner layout was reworked:
|
||||||
|
- `Tutup` moved outside the camera panel
|
||||||
|
- scanner panel wrapped as its own card/section
|
||||||
|
- manual input visually separated from camera section
|
||||||
|
- scanner corner markers fixed so their directions are correct
|
||||||
|
|
||||||
|
### Washing
|
||||||
|
- `Lot` picker uses search / scan workflow
|
||||||
|
- `Tempat Cuci` changed to simple dropdown
|
||||||
|
- card action button `Selesaikan` forced to one line by reducing font and spacing
|
||||||
|
|
||||||
|
## Known Gaps / Things Not Yet Wired
|
||||||
|
- Some modules are present as minimum mobile flows only, aligned to blueprint, not full web parity.
|
||||||
|
- `lot_transformations` still needs full create flow audit if expanded beyond current scope.
|
||||||
|
- Some modules are read-only where blueprint only required read paths.
|
||||||
|
- `pindah lokasi` and `ubah status` for lot detail are not implemented, and UI was intentionally removed.
|
||||||
|
|
||||||
|
## Build / Deploy
|
||||||
|
- Android SDK path used locally: `/opt/android-sdk`
|
||||||
|
- Typical build command:
|
||||||
|
- `./gradlew assembleDebug`
|
||||||
|
- Typical install command:
|
||||||
|
- `adb install -r app/build/outputs/apk/debug/app-debug.apk`
|
||||||
|
- Last confirmed installed package timestamp during this session:
|
||||||
|
- `lastUpdateTime=2026-05-21 23:29:49`
|
||||||
|
|
||||||
|
## Current Device
|
||||||
|
- Frequently used connected device:
|
||||||
|
- `CPH2781` (Oppo)
|
||||||
|
|
||||||
|
## Notes For Next Codex
|
||||||
|
- Use `mobile-api-blueprint.md` as the primary spec.
|
||||||
|
- Use web project only as a secondary reference for payload shape or flow hints, not as the source of truth.
|
||||||
|
- Be careful with nested `Scaffold` padding/insets; several UI gaps came from double insets.
|
||||||
|
- Many server responses differ slightly between endpoints; avoid assuming detail response shape matches scan/list response shape.
|
||||||
86
app/build.gradle.kts
Normal file
86
app/build.gradle.kts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
id("org.jetbrains.kotlin.android")
|
||||||
|
id("org.jetbrains.kotlin.plugin.compose")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "id.abelbirdnest.mobile"
|
||||||
|
compileSdk = 35
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "id.abelbirdnest.mobile"
|
||||||
|
minSdk = 26
|
||||||
|
targetSdk = 35
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
vectorDrawables {
|
||||||
|
useSupportLibrary = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
|
|
||||||
|
packaging {
|
||||||
|
resources {
|
||||||
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget.set(JvmTarget.JVM_17)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
val composeBom = platform("androidx.compose:compose-bom:2024.12.01")
|
||||||
|
|
||||||
|
implementation(composeBom)
|
||||||
|
androidTestImplementation(composeBom)
|
||||||
|
|
||||||
|
implementation("androidx.core:core-ktx:1.15.0")
|
||||||
|
implementation("androidx.activity:activity-compose:1.10.1")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
|
||||||
|
implementation("androidx.compose.ui:ui")
|
||||||
|
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||||
|
implementation("androidx.compose.foundation:foundation")
|
||||||
|
implementation("androidx.compose.material3:material3")
|
||||||
|
implementation("androidx.compose.material:material-icons-extended")
|
||||||
|
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
|
||||||
|
implementation("com.squareup.retrofit2:retrofit:2.11.0")
|
||||||
|
implementation("com.squareup.retrofit2:converter-gson:2.11.0")
|
||||||
|
implementation("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
|
||||||
|
implementation("androidx.camera:camera-camera2:1.4.1")
|
||||||
|
implementation("androidx.camera:camera-lifecycle:1.4.1")
|
||||||
|
implementation("androidx.camera:camera-view:1.4.1")
|
||||||
|
implementation("com.google.mlkit:barcode-scanning:17.3.0")
|
||||||
|
|
||||||
|
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||||
|
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||||
|
}
|
||||||
1
app/proguard-rules.pro
vendored
Normal file
1
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Intentionally empty for now.
|
||||||
25
app/src/main/AndroidManifest.xml
Normal file
25
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@drawable/logo_abelbirdnest"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@drawable/logo_abelbirdnest"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
25
app/src/main/java/id/abelbirdnest/mobile/MainActivity.kt
Normal file
25
app/src/main/java/id/abelbirdnest/mobile/MainActivity.kt
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package id.abelbirdnest.mobile
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import id.abelbirdnest.mobile.ui.AbelbirdnestApp
|
||||||
|
import id.abelbirdnest.mobile.ui.MainViewModel
|
||||||
|
import id.abelbirdnest.mobile.ui.theme.AbelbirdnestTheme
|
||||||
|
|
||||||
|
class MainActivity : ComponentActivity() {
|
||||||
|
private val viewModel by viewModels<MainViewModel>()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
AbelbirdnestTheme {
|
||||||
|
AbelbirdnestApp(viewModel = viewModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,324 @@
|
|||||||
|
package id.abelbirdnest.mobile.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import id.abelbirdnest.mobile.data.toLotDetailData
|
||||||
|
import id.abelbirdnest.mobile.network.ApiFactory
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import retrofit2.HttpException
|
||||||
|
|
||||||
|
class MobileRepository(context: Context) {
|
||||||
|
private val api = ApiFactory.create()
|
||||||
|
private val sessionStore = SessionStore(context)
|
||||||
|
private val gson = Gson()
|
||||||
|
|
||||||
|
fun sessionToken(): String? = sessionStore.token()
|
||||||
|
|
||||||
|
suspend fun login(identity: String, password: String): LoginData {
|
||||||
|
val response = api.login(LoginRequest(identity = identity, password = password))
|
||||||
|
sessionStore.save(response.data)
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadDashboardBundle(): DashboardBundle = coroutineScope {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
val auth = "Bearer $token"
|
||||||
|
|
||||||
|
val bootstrap = async { api.bootstrap(auth).data }
|
||||||
|
val dashboard = async { api.dashboard(auth).data }
|
||||||
|
|
||||||
|
val bootstrapData = bootstrap.await()
|
||||||
|
val dashboardData = dashboard.await()
|
||||||
|
val lotItems = if (bootstrapData.modules.contains("lots")) {
|
||||||
|
try {
|
||||||
|
api.lots(auth).data
|
||||||
|
} catch (error: HttpException) {
|
||||||
|
if (error.code() == 403) emptyList() else throw error
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
DashboardBundle(
|
||||||
|
user = bootstrapData.user,
|
||||||
|
modules = bootstrapData.modules,
|
||||||
|
summary = bootstrapData.summary,
|
||||||
|
metrics = dashboardData.metrics,
|
||||||
|
criticalLots = dashboardData.criticalLots,
|
||||||
|
recentActivity = dashboardData.recentActivity,
|
||||||
|
gradeDistribution = lotItems
|
||||||
|
.filter { it.availableQty > 0 }
|
||||||
|
.groupBy { it.grade }
|
||||||
|
.mapValues { entry -> entry.value.sumOf { it.availableQty } }
|
||||||
|
.entries
|
||||||
|
.sortedByDescending { it.value }
|
||||||
|
.take(3)
|
||||||
|
.let { topGrades ->
|
||||||
|
val total = topGrades.sumOf { it.value }.takeIf { it > 0 } ?: 1.0
|
||||||
|
topGrades.map { grade ->
|
||||||
|
GradeDistribution(
|
||||||
|
grade = grade.key,
|
||||||
|
quantity = grade.value,
|
||||||
|
percentage = ((grade.value / total) * 100).toInt(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transformationTypes = bootstrapData.transformationTypes,
|
||||||
|
remainderModes = bootstrapData.remainderModes,
|
||||||
|
processingLossModes = bootstrapData.processingLossModes,
|
||||||
|
grades = bootstrapData.grades,
|
||||||
|
warehouses = bootstrapData.warehouses,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadLots(): List<LotItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.lots("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadLotDetail(id: String): LotDetailData {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.lotDetail("Bearer $token", id).data.toLotDetailData()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun scanLot(code: String): LotScanResult {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return LotScanResult(
|
||||||
|
scannedCode = code,
|
||||||
|
scannedAtMillis = System.currentTimeMillis(),
|
||||||
|
payload = api.scanLot("Bearer $token", code).data,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadAdjustmentReasons(): List<AdjustmentReason> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.adjustmentReasons("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadStockAdjustments(): List<StockAdjustmentListItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.stockAdjustments("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createStockAdjustment(payload: StockAdjustmentCreatePayload): StockAdjustmentListItem {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.createStockAdjustment("Bearer $token", payload).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadPurchases(): List<PurchaseListItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.purchases("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadUnits(): List<UnitLookup> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.units("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadEmployees(): List<LookupRecord> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.employees("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createPurchase(payload: PurchaseCreatePayload): PurchaseDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.createPurchase("Bearer $token", payload).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadPurchaseDetail(id: String): PurchaseDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.purchaseDetail("Bearer $token", id).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updatePurchase(id: String, payload: PurchaseCreatePayload): PurchaseDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.updatePurchase("Bearer $token", id, payload).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadPurchaseAnalyses(): List<PurchaseAnalysisListItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.purchaseAnalyses("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadPurchaseAnalysisDetail(purchaseId: String): PurchaseAnalysisDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.purchaseAnalysisDetail("Bearer $token", purchaseId).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadPurchaseRealizations(): List<PurchaseRealizationListItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.purchaseRealizations("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadPurchaseRealizationDetail(purchaseId: String): PurchaseRealizationDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.purchaseRealizationDetail("Bearer $token", purchaseId).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadRegularSales(): List<RegularSaleListItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.regularSales("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadRegularSaleDetail(id: String): RegularSaleDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.regularSaleDetail("Bearer $token", id).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun closeRegularSale(id: String, payload: RegularSaleClosePayload): RegularSaleDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.closeRegularSale("Bearer $token", id, payload).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadJitSales(): List<JitSaleListItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.jitSales("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadJitSaleDetail(id: String): JitSaleDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.jitSaleDetail("Bearer $token", id).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun closeJitSale(id: String, payload: JitSaleClosePayload): JitSaleDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.closeJitSale("Bearer $token", id, payload).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadConsignmentsBootstrap(): ConsignmentBootstrapData {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.consignmentsBootstrap("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadConsignments(): List<ConsignmentListItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.consignments("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadConsignmentDetail(id: String): ConsignmentDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.consignmentDetail("Bearer $token", id).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun closeConsignmentLine(lineId: String, payload: ConsignmentCloseLinePayload) {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
api.closeConsignmentLine("Bearer $token", lineId, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadFundRequestsBootstrap(): FundRequestsBootstrapData {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.fundRequestsBootstrap("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadFundRequests(): List<FundRequestListItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.fundRequests("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createFundRequest(
|
||||||
|
transferType: String,
|
||||||
|
referenceNo: String,
|
||||||
|
agentId: String,
|
||||||
|
agentBankAccountId: String,
|
||||||
|
companyBankAccountId: String,
|
||||||
|
amount: String,
|
||||||
|
transferredAt: String,
|
||||||
|
): FundRequestListItem {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
val plain = "text/plain".toMediaType()
|
||||||
|
val fields = linkedMapOf(
|
||||||
|
"transfer_type" to transferType.toRequestBody(plain),
|
||||||
|
"reference_no" to referenceNo.toRequestBody(plain),
|
||||||
|
"agent_id" to agentId.toRequestBody(plain),
|
||||||
|
"agent_bank_account_id" to agentBankAccountId.toRequestBody(plain),
|
||||||
|
"company_bank_account_id" to companyBankAccountId.toRequestBody(plain),
|
||||||
|
"amount" to amount.toRequestBody(plain),
|
||||||
|
"transferred_at" to transferredAt.toRequestBody(plain),
|
||||||
|
)
|
||||||
|
return api.createFundRequest("Bearer $token", fields).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun submitPurchase(id: String): PurchaseDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.submitPurchase("Bearer $token", id).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun cancelPurchase(id: String) {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
api.cancelPurchase("Bearer $token", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadLotTransformations(): List<LotTransformationListItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.lotTransformations("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadLotTransformationDetail(id: String): LotTransformationDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.lotTransformationDetail("Bearer $token", id).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createLotTransformation(payload: LotTransformationCreatePayload): LotTransformationDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.createLotTransformation("Bearer $token", payload).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadWashingBootstrap(): WashingBootstrapData {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.washingBootstrap("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadWashings(): List<WashingListItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.washings("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createWashing(payload: WashingCreatePayload): WashingListItem {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
val body = gson.toJson(payload).toRequestBody("text/plain".toMediaType())
|
||||||
|
return api.createWashing("Bearer $token", body).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateWashing(id: String, payload: WashingCreatePayload): WashingListItem {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
val body = gson.toJson(payload).toRequestBody("text/plain".toMediaType())
|
||||||
|
return api.updateWashing("Bearer $token", id, body).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun completeWashing(id: String, payload: CompleteWashingPayload): WashingListItem {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.completeWashing("Bearer $token", id, payload).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadReceiptsBootstrap(): ReceiptBootstrapData {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.receiptsBootstrap("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadReceipts(): List<ReceiptListItem> {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.receipts("Bearer $token").data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun loadReceiptDetail(id: String): ReceiptDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.receiptDetail("Bearer $token", id).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun createReceipt(payload: ReceiptCreatePayload): ReceiptDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.createReceipt("Bearer $token", payload).data
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun generateReceiptLots(id: String): ReceiptDetail {
|
||||||
|
val token = sessionStore.token() ?: error("Sesi tidak tersedia")
|
||||||
|
return api.generateReceiptLots("Bearer $token", id).data
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logout() {
|
||||||
|
sessionStore.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
1152
app/src/main/java/id/abelbirdnest/mobile/data/Models.kt
Normal file
1152
app/src/main/java/id/abelbirdnest/mobile/data/Models.kt
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@
|
|||||||
|
package id.abelbirdnest.mobile.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
class SessionStore(context: Context) {
|
||||||
|
private val prefs = context.getSharedPreferences("abelbirdnest_stock_session", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
fun save(loginData: LoginData) {
|
||||||
|
prefs.edit()
|
||||||
|
.putString(KEY_TOKEN, loginData.sessionToken)
|
||||||
|
.putString(KEY_NAME, loginData.user.name)
|
||||||
|
.putString(KEY_ROLE, loginData.user.role)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun token(): String? = prefs.getString(KEY_TOKEN, null)
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
prefs.edit().clear().apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val KEY_TOKEN = "token"
|
||||||
|
private const val KEY_NAME = "name"
|
||||||
|
private const val KEY_ROLE = "role"
|
||||||
|
}
|
||||||
|
}
|
||||||
352
app/src/main/java/id/abelbirdnest/mobile/network/ApiService.kt
Normal file
352
app/src/main/java/id/abelbirdnest/mobile/network/ApiService.kt
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
package id.abelbirdnest.mobile.network
|
||||||
|
|
||||||
|
import id.abelbirdnest.mobile.data.ApiEnvelope
|
||||||
|
import id.abelbirdnest.mobile.data.BootstrapData
|
||||||
|
import id.abelbirdnest.mobile.data.CompleteWashingPayload
|
||||||
|
import id.abelbirdnest.mobile.data.ConsignmentBootstrapData
|
||||||
|
import id.abelbirdnest.mobile.data.ConsignmentCloseLinePayload
|
||||||
|
import id.abelbirdnest.mobile.data.ConsignmentDetail
|
||||||
|
import id.abelbirdnest.mobile.data.ConsignmentListItem
|
||||||
|
import id.abelbirdnest.mobile.data.DashboardData
|
||||||
|
import id.abelbirdnest.mobile.data.FundRequestListItem
|
||||||
|
import id.abelbirdnest.mobile.data.FundRequestsBootstrapData
|
||||||
|
import id.abelbirdnest.mobile.data.JitSaleClosePayload
|
||||||
|
import id.abelbirdnest.mobile.data.JitSaleDetail
|
||||||
|
import id.abelbirdnest.mobile.data.JitSaleListItem
|
||||||
|
import id.abelbirdnest.mobile.data.LookupRecord
|
||||||
|
import id.abelbirdnest.mobile.data.LotDetailResponse
|
||||||
|
import id.abelbirdnest.mobile.data.LotTransformationCreatePayload
|
||||||
|
import id.abelbirdnest.mobile.data.LotTransformationDetail
|
||||||
|
import id.abelbirdnest.mobile.data.LotTransformationListItem
|
||||||
|
import id.abelbirdnest.mobile.data.AdjustmentReason
|
||||||
|
import id.abelbirdnest.mobile.data.PurchaseAnalysisDetail
|
||||||
|
import id.abelbirdnest.mobile.data.PurchaseAnalysisListItem
|
||||||
|
import id.abelbirdnest.mobile.data.PurchaseCreatePayload
|
||||||
|
import id.abelbirdnest.mobile.data.PurchaseDetail
|
||||||
|
import id.abelbirdnest.mobile.data.PurchaseListItem
|
||||||
|
import id.abelbirdnest.mobile.data.PurchaseRealizationDetail
|
||||||
|
import id.abelbirdnest.mobile.data.PurchaseRealizationListItem
|
||||||
|
import id.abelbirdnest.mobile.data.RegularSaleClosePayload
|
||||||
|
import id.abelbirdnest.mobile.data.RegularSaleDetail
|
||||||
|
import id.abelbirdnest.mobile.data.RegularSaleListItem
|
||||||
|
import id.abelbirdnest.mobile.data.ReceiptBootstrapData
|
||||||
|
import id.abelbirdnest.mobile.data.StockAdjustmentCreatePayload
|
||||||
|
import id.abelbirdnest.mobile.data.StockAdjustmentListItem
|
||||||
|
import id.abelbirdnest.mobile.data.ReceiptCreatePayload
|
||||||
|
import id.abelbirdnest.mobile.data.ReceiptDetail
|
||||||
|
import id.abelbirdnest.mobile.data.ReceiptListItem
|
||||||
|
import id.abelbirdnest.mobile.data.LoginRequest
|
||||||
|
import id.abelbirdnest.mobile.data.LoginResponse
|
||||||
|
import id.abelbirdnest.mobile.data.LotItem
|
||||||
|
import id.abelbirdnest.mobile.data.LotScanPayload
|
||||||
|
import id.abelbirdnest.mobile.data.UnitLookup
|
||||||
|
import id.abelbirdnest.mobile.data.WashingBootstrapData
|
||||||
|
import id.abelbirdnest.mobile.data.WashingListItem
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Header
|
||||||
|
import retrofit2.http.Multipart
|
||||||
|
import retrofit2.http.Part
|
||||||
|
import retrofit2.http.PartMap
|
||||||
|
import retrofit2.http.POST
|
||||||
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.Query
|
||||||
|
import retrofit2.http.PUT
|
||||||
|
|
||||||
|
interface ApiService {
|
||||||
|
@POST("auth/login")
|
||||||
|
suspend fun login(
|
||||||
|
@Body request: LoginRequest,
|
||||||
|
): LoginResponse
|
||||||
|
|
||||||
|
@GET("mobile/bootstrap")
|
||||||
|
suspend fun bootstrap(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<BootstrapData>
|
||||||
|
|
||||||
|
@GET("mobile/dashboard")
|
||||||
|
suspend fun dashboard(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Query("locale") locale: String = "id",
|
||||||
|
): ApiEnvelope<DashboardData>
|
||||||
|
|
||||||
|
@GET("mobile/lots")
|
||||||
|
suspend fun lots(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<LotItem>>
|
||||||
|
|
||||||
|
@GET("mobile/lots/{id}")
|
||||||
|
suspend fun lotDetail(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
): ApiEnvelope<LotDetailResponse>
|
||||||
|
|
||||||
|
@GET("mobile/lots/scan")
|
||||||
|
suspend fun scanLot(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Query("code") code: String,
|
||||||
|
): ApiEnvelope<LotScanPayload>
|
||||||
|
|
||||||
|
@GET("adjustment-reasons")
|
||||||
|
suspend fun adjustmentReasons(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<AdjustmentReason>>
|
||||||
|
|
||||||
|
@GET("mobile/stock-adjustments")
|
||||||
|
suspend fun stockAdjustments(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<StockAdjustmentListItem>>
|
||||||
|
|
||||||
|
@POST("mobile/stock-adjustments")
|
||||||
|
suspend fun createStockAdjustment(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Body payload: StockAdjustmentCreatePayload,
|
||||||
|
): ApiEnvelope<StockAdjustmentListItem>
|
||||||
|
|
||||||
|
@GET("mobile/purchases")
|
||||||
|
suspend fun purchases(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<PurchaseListItem>>
|
||||||
|
|
||||||
|
@GET("units")
|
||||||
|
suspend fun units(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<UnitLookup>>
|
||||||
|
|
||||||
|
@GET("employees")
|
||||||
|
suspend fun employees(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<LookupRecord>>
|
||||||
|
|
||||||
|
@POST("mobile/purchases")
|
||||||
|
suspend fun createPurchase(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Body payload: PurchaseCreatePayload,
|
||||||
|
): ApiEnvelope<PurchaseDetail>
|
||||||
|
|
||||||
|
@GET("mobile/purchases/{id}")
|
||||||
|
suspend fun purchaseDetail(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
): ApiEnvelope<PurchaseDetail>
|
||||||
|
|
||||||
|
@PUT("mobile/purchases/{id}")
|
||||||
|
suspend fun updatePurchase(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
@Body payload: PurchaseCreatePayload,
|
||||||
|
): ApiEnvelope<PurchaseDetail>
|
||||||
|
|
||||||
|
@GET("mobile/purchase-analyses")
|
||||||
|
suspend fun purchaseAnalyses(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<PurchaseAnalysisListItem>>
|
||||||
|
|
||||||
|
@GET("mobile/purchase-analyses/{purchaseId}")
|
||||||
|
suspend fun purchaseAnalysisDetail(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("purchaseId") purchaseId: String,
|
||||||
|
): ApiEnvelope<PurchaseAnalysisDetail>
|
||||||
|
|
||||||
|
@GET("mobile/purchase-realizations")
|
||||||
|
suspend fun purchaseRealizations(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<PurchaseRealizationListItem>>
|
||||||
|
|
||||||
|
@GET("mobile/purchase-realizations/{purchaseId}")
|
||||||
|
suspend fun purchaseRealizationDetail(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("purchaseId") purchaseId: String,
|
||||||
|
): ApiEnvelope<PurchaseRealizationDetail>
|
||||||
|
|
||||||
|
@GET("mobile/sales-regular")
|
||||||
|
suspend fun regularSales(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<RegularSaleListItem>>
|
||||||
|
|
||||||
|
@GET("mobile/sales-regular/{id}")
|
||||||
|
suspend fun regularSaleDetail(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
): ApiEnvelope<RegularSaleDetail>
|
||||||
|
|
||||||
|
@POST("mobile/sales-regular/{id}/close")
|
||||||
|
suspend fun closeRegularSale(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
@Body payload: RegularSaleClosePayload,
|
||||||
|
): ApiEnvelope<RegularSaleDetail>
|
||||||
|
|
||||||
|
@GET("mobile/sales-jit")
|
||||||
|
suspend fun jitSales(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<JitSaleListItem>>
|
||||||
|
|
||||||
|
@GET("mobile/sales-jit/{id}")
|
||||||
|
suspend fun jitSaleDetail(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
): ApiEnvelope<JitSaleDetail>
|
||||||
|
|
||||||
|
@POST("mobile/sales-jit/{id}/close")
|
||||||
|
suspend fun closeJitSale(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
@Body payload: JitSaleClosePayload,
|
||||||
|
): ApiEnvelope<JitSaleDetail>
|
||||||
|
|
||||||
|
@GET("mobile/consignments/bootstrap")
|
||||||
|
suspend fun consignmentsBootstrap(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<ConsignmentBootstrapData>
|
||||||
|
|
||||||
|
@GET("mobile/consignments")
|
||||||
|
suspend fun consignments(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<ConsignmentListItem>>
|
||||||
|
|
||||||
|
@GET("mobile/consignments/{id}")
|
||||||
|
suspend fun consignmentDetail(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
): ApiEnvelope<ConsignmentDetail>
|
||||||
|
|
||||||
|
@POST("consignments/lines/{lineId}/close")
|
||||||
|
suspend fun closeConsignmentLine(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("lineId") lineId: String,
|
||||||
|
@Body payload: ConsignmentCloseLinePayload,
|
||||||
|
): ApiEnvelope<Map<String, Any>>
|
||||||
|
|
||||||
|
@GET("mobile/fund-requests/bootstrap")
|
||||||
|
suspend fun fundRequestsBootstrap(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<FundRequestsBootstrapData>
|
||||||
|
|
||||||
|
@GET("mobile/fund-requests")
|
||||||
|
suspend fun fundRequests(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<FundRequestListItem>>
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST("mobile/fund-requests")
|
||||||
|
suspend fun createFundRequest(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@PartMap fields: Map<String, @JvmSuppressWildcards RequestBody>,
|
||||||
|
): ApiEnvelope<FundRequestListItem>
|
||||||
|
|
||||||
|
@POST("mobile/purchases/{id}/submit")
|
||||||
|
suspend fun submitPurchase(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
): ApiEnvelope<PurchaseDetail>
|
||||||
|
|
||||||
|
@POST("mobile/purchases/{id}/cancel")
|
||||||
|
suspend fun cancelPurchase(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
): ApiEnvelope<Map<String, Any>>
|
||||||
|
|
||||||
|
@GET("mobile/lot-transformations")
|
||||||
|
suspend fun lotTransformations(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<LotTransformationListItem>>
|
||||||
|
|
||||||
|
@GET("mobile/lot-transformations/{id}")
|
||||||
|
suspend fun lotTransformationDetail(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
): ApiEnvelope<LotTransformationDetail>
|
||||||
|
|
||||||
|
@POST("mobile/lot-transformations")
|
||||||
|
suspend fun createLotTransformation(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Body payload: LotTransformationCreatePayload,
|
||||||
|
): ApiEnvelope<LotTransformationDetail>
|
||||||
|
|
||||||
|
@GET("mobile/washing/bootstrap")
|
||||||
|
suspend fun washingBootstrap(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<WashingBootstrapData>
|
||||||
|
|
||||||
|
@GET("mobile/washing")
|
||||||
|
suspend fun washings(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<WashingListItem>>
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST("mobile/washing")
|
||||||
|
suspend fun createWashing(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Part("payload") payload: RequestBody,
|
||||||
|
): ApiEnvelope<WashingListItem>
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@PUT("mobile/washing/{id}")
|
||||||
|
suspend fun updateWashing(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
@Part("payload") payload: RequestBody,
|
||||||
|
): ApiEnvelope<WashingListItem>
|
||||||
|
|
||||||
|
@POST("mobile/washing/{id}/complete")
|
||||||
|
suspend fun completeWashing(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
@Body payload: CompleteWashingPayload,
|
||||||
|
): ApiEnvelope<WashingListItem>
|
||||||
|
|
||||||
|
@GET("mobile/receipts/bootstrap")
|
||||||
|
suspend fun receiptsBootstrap(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<ReceiptBootstrapData>
|
||||||
|
|
||||||
|
@GET("mobile/receipts")
|
||||||
|
suspend fun receipts(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
): ApiEnvelope<List<ReceiptListItem>>
|
||||||
|
|
||||||
|
@POST("mobile/receipts")
|
||||||
|
suspend fun createReceipt(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Body payload: ReceiptCreatePayload,
|
||||||
|
): ApiEnvelope<ReceiptDetail>
|
||||||
|
|
||||||
|
@GET("mobile/receipts/{id}")
|
||||||
|
suspend fun receiptDetail(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
): ApiEnvelope<ReceiptDetail>
|
||||||
|
|
||||||
|
@POST("mobile/receipts/{id}/generate-lots")
|
||||||
|
suspend fun generateReceiptLots(
|
||||||
|
@Header("Authorization") authorization: String,
|
||||||
|
@Path("id") id: String,
|
||||||
|
): ApiEnvelope<ReceiptDetail>
|
||||||
|
}
|
||||||
|
|
||||||
|
object ApiFactory {
|
||||||
|
private const val BASE_URL = "https://abelbirdnest.id/api/v1/"
|
||||||
|
|
||||||
|
fun create(): ApiService {
|
||||||
|
val logger = HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.BASIC
|
||||||
|
}
|
||||||
|
|
||||||
|
val client = OkHttpClient.Builder()
|
||||||
|
.addInterceptor(logger)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return Retrofit.Builder()
|
||||||
|
.baseUrl(BASE_URL)
|
||||||
|
.client(client)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
.create(ApiService::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
7667
app/src/main/java/id/abelbirdnest/mobile/ui/AbelbirdnestApp.kt
Normal file
7667
app/src/main/java/id/abelbirdnest/mobile/ui/AbelbirdnestApp.kt
Normal file
File diff suppressed because it is too large
Load Diff
3231
app/src/main/java/id/abelbirdnest/mobile/ui/MainViewModel.kt
Normal file
3231
app/src/main/java/id/abelbirdnest/mobile/ui/MainViewModel.kt
Normal file
File diff suppressed because it is too large
Load Diff
21
app/src/main/java/id/abelbirdnest/mobile/ui/theme/Color.kt
Normal file
21
app/src/main/java/id/abelbirdnest/mobile/ui/theme/Color.kt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package id.abelbirdnest.mobile.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val Background = Color(0xFFF8FAFA)
|
||||||
|
val Surface = Color(0xFFF8FAFA)
|
||||||
|
val SurfaceContainerLowest = Color(0xFFFFFFFF)
|
||||||
|
val SurfaceContainerHigh = Color(0xFFE6E8E9)
|
||||||
|
val SurfaceContainer = Color(0xFFECEEEE)
|
||||||
|
val OnSurface = Color(0xFF191C1D)
|
||||||
|
val OnSurfaceVariant = Color(0xFF3F484A)
|
||||||
|
val Outline = Color(0xFF6F797A)
|
||||||
|
val OutlineVariant = Color(0xFFBFC8CA)
|
||||||
|
val Primary = Color(0xFF00454C)
|
||||||
|
val PrimaryContainer = Color(0xFF0D5E67)
|
||||||
|
val OnPrimary = Color(0xFFFFFFFF)
|
||||||
|
val OnPrimaryContainer = Color(0xFF92D5DF)
|
||||||
|
val Secondary = Color(0xFF4E6073)
|
||||||
|
val SecondaryContainer = Color(0xFFCFE2F9)
|
||||||
|
val Tertiary = Color(0xFF60320F)
|
||||||
|
val Error = Color(0xFFBA1A1A)
|
||||||
87
app/src/main/java/id/abelbirdnest/mobile/ui/theme/Theme.kt
Normal file
87
app/src/main/java/id/abelbirdnest/mobile/ui/theme/Theme.kt
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
package id.abelbirdnest.mobile.ui.theme
|
||||||
|
|
||||||
|
import androidx.compose.material3.ColorScheme
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Typography
|
||||||
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.material3.lightColorScheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
private val AppColorScheme: ColorScheme = lightColorScheme(
|
||||||
|
primary = Primary,
|
||||||
|
onPrimary = OnPrimary,
|
||||||
|
primaryContainer = PrimaryContainer,
|
||||||
|
onPrimaryContainer = OnPrimaryContainer,
|
||||||
|
secondary = Secondary,
|
||||||
|
secondaryContainer = SecondaryContainer,
|
||||||
|
tertiary = Tertiary,
|
||||||
|
error = Error,
|
||||||
|
background = Background,
|
||||||
|
surface = Surface,
|
||||||
|
surfaceContainer = SurfaceContainer,
|
||||||
|
surfaceContainerHigh = SurfaceContainerHigh,
|
||||||
|
surfaceContainerLowest = SurfaceContainerLowest,
|
||||||
|
onSurface = OnSurface,
|
||||||
|
onSurfaceVariant = OnSurfaceVariant,
|
||||||
|
outline = Outline,
|
||||||
|
outlineVariant = OutlineVariant,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val AppTypography = Typography(
|
||||||
|
headlineMedium = TextStyle(
|
||||||
|
fontFamily = FontFamily.SansSerif,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 28.sp,
|
||||||
|
lineHeight = 32.sp,
|
||||||
|
),
|
||||||
|
headlineSmall = TextStyle(
|
||||||
|
fontFamily = FontFamily.SansSerif,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
lineHeight = 30.sp,
|
||||||
|
),
|
||||||
|
titleMedium = TextStyle(
|
||||||
|
fontFamily = FontFamily.SansSerif,
|
||||||
|
fontWeight = FontWeight.SemiBold,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
lineHeight = 22.sp,
|
||||||
|
),
|
||||||
|
bodyMedium = TextStyle(
|
||||||
|
fontFamily = FontFamily.SansSerif,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
lineHeight = 20.sp,
|
||||||
|
),
|
||||||
|
bodySmall = TextStyle(
|
||||||
|
fontFamily = FontFamily.SansSerif,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
lineHeight = 18.sp,
|
||||||
|
),
|
||||||
|
labelSmall = TextStyle(
|
||||||
|
fontFamily = FontFamily.SansSerif,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 11.sp,
|
||||||
|
lineHeight = 16.sp,
|
||||||
|
letterSpacing = 0.6.sp,
|
||||||
|
),
|
||||||
|
displaySmall = TextStyle(
|
||||||
|
fontFamily = FontFamily.SansSerif,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 34.sp,
|
||||||
|
lineHeight = 40.sp,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AbelbirdnestTheme(content: @Composable () -> Unit) {
|
||||||
|
MaterialTheme(
|
||||||
|
colorScheme = AppColorScheme,
|
||||||
|
typography = AppTypography,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
package id.abelbirdnest.mobile.ui.theme
|
||||||
|
|
||||||
|
// Typography is defined in Theme.kt.
|
||||||
BIN
app/src/main/res/drawable/logo_abelbirdnest.png
Executable file
BIN
app/src/main/res/drawable/logo_abelbirdnest.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
3
app/src/main/res/values/strings.xml
Normal file
3
app/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">Abelbirdnest Stock</string>
|
||||||
|
</resources>
|
||||||
5
build.gradle.kts
Normal file
5
build.gradle.kts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application") version "8.7.3" apply false
|
||||||
|
id("org.jetbrains.kotlin.android") version "2.0.21" apply false
|
||||||
|
id("org.jetbrains.kotlin.plugin.compose") version "2.0.21" apply false
|
||||||
|
}
|
||||||
5
gradle.properties
Normal file
5
gradle.properties
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
|
||||||
|
org.gradle.java.home=/Library/Java/JavaVirtualMachines/temurin-21.jdk/Contents/Home
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.nonTransitiveRClass=true
|
||||||
|
kotlin.code.style=official
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
9
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
9
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
retries=0
|
||||||
|
retryBackOffMs=500
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
248
gradlew
vendored
Executable file
248
gradlew
vendored
Executable file
@ -0,0 +1,248 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
82
gradlew.bat
vendored
Normal file
82
gradlew.bat
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables, and ensure extensions are enabled
|
||||||
|
setlocal EnableExtensions
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
"%COMSPEC%" /c exit 1
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
|
||||||
|
@rem which allows us to clear the local environment before executing the java command
|
||||||
|
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
|
||||||
|
|
||||||
|
:exitWithErrorLevel
|
||||||
|
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
|
||||||
|
"%COMSPEC%" /c exit %ERRORLEVEL%
|
||||||
BIN
logo_abelbirdnest.png
Executable file
BIN
logo_abelbirdnest.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 120 KiB |
268
mobile-api-blueprint.md
Normal file
268
mobile-api-blueprint.md
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
# Mobile API Blueprint
|
||||||
|
|
||||||
|
Dokumen ini merangkum:
|
||||||
|
- endpoint mobile per role
|
||||||
|
- layar minimum yang dibutuhkan aplikasi mobile
|
||||||
|
- batas scope mobile yang sengaja dipertahankan agar tetap praktis
|
||||||
|
|
||||||
|
## Prinsip
|
||||||
|
|
||||||
|
- Mobile memakai auth yang sama dengan web.
|
||||||
|
- Login lewat `POST /api/v1/auth/login`, lalu kirim `Authorization: Bearer <session_token>`.
|
||||||
|
- Semua endpoint mobile berada di prefix `/api/v1/mobile`.
|
||||||
|
- Mobile fokus ke operasi cepat, scan, input lapangan, monitoring, dan closing ringan.
|
||||||
|
- Fitur admin berat seperti `users`, `settings`, `audit-trail`, dan master data lengkap tetap web-only.
|
||||||
|
|
||||||
|
## Role Mobile
|
||||||
|
|
||||||
|
Role yang didukung di mobile:
|
||||||
|
- `WAREHOUSE`
|
||||||
|
- `QC`
|
||||||
|
- `SALES`
|
||||||
|
- `PURCHASING`
|
||||||
|
- `OWNER`
|
||||||
|
|
||||||
|
Role yang tidak menjadi target mobile utama:
|
||||||
|
- `ADMIN`
|
||||||
|
- `SYSTEM_ADMIN`
|
||||||
|
|
||||||
|
## Bootstrap Umum
|
||||||
|
|
||||||
|
Semua role mobile memakai:
|
||||||
|
- `GET /api/v1/mobile/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/dashboard?locale=id|en`
|
||||||
|
|
||||||
|
`/mobile/bootstrap` mengembalikan:
|
||||||
|
- user session
|
||||||
|
- daftar modul yang boleh diakses role tersebut
|
||||||
|
- summary ringkas operasional
|
||||||
|
- grade aktif
|
||||||
|
- gudang aktif dan lokasi aktif
|
||||||
|
- master transformation mode
|
||||||
|
|
||||||
|
## Matriks Endpoint Per Role
|
||||||
|
|
||||||
|
### Warehouse
|
||||||
|
|
||||||
|
Tujuan:
|
||||||
|
- scan lot
|
||||||
|
- submit purchase yang otomatis membuat receipt dan lot
|
||||||
|
- buat penyesuaian stok
|
||||||
|
- kirim / selesaikan washing
|
||||||
|
|
||||||
|
Endpoint:
|
||||||
|
- `GET /api/v1/mobile/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/dashboard`
|
||||||
|
- `GET /api/v1/mobile/lots`
|
||||||
|
- `GET /api/v1/mobile/lots/:id`
|
||||||
|
- `GET /api/v1/mobile/lots/scan?code=...`
|
||||||
|
- `GET /api/v1/mobile/purchases`
|
||||||
|
- `GET /api/v1/mobile/purchases/:id`
|
||||||
|
- `POST /api/v1/mobile/purchases/:id/submit`
|
||||||
|
- `GET /api/v1/mobile/stock-adjustments/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/stock-adjustments`
|
||||||
|
- `POST /api/v1/mobile/stock-adjustments`
|
||||||
|
- `GET /api/v1/mobile/washing/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/washing`
|
||||||
|
- `POST /api/v1/mobile/washing`
|
||||||
|
- `PUT /api/v1/mobile/washing/:id`
|
||||||
|
- `POST /api/v1/mobile/washing/:id/complete`
|
||||||
|
|
||||||
|
### QC
|
||||||
|
|
||||||
|
Tujuan:
|
||||||
|
- scan lot
|
||||||
|
- lihat detail lineage lot
|
||||||
|
- ubah grade / mixing
|
||||||
|
- bantu washing completion
|
||||||
|
- buat penyesuaian stok terkait QC
|
||||||
|
|
||||||
|
Endpoint:
|
||||||
|
- `GET /api/v1/mobile/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/dashboard`
|
||||||
|
- `GET /api/v1/mobile/lots`
|
||||||
|
- `GET /api/v1/mobile/lots/:id`
|
||||||
|
- `GET /api/v1/mobile/lots/scan?code=...`
|
||||||
|
- `GET /api/v1/mobile/washing/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/washing`
|
||||||
|
- `PUT /api/v1/mobile/washing/:id`
|
||||||
|
- `POST /api/v1/mobile/washing/:id/complete`
|
||||||
|
- `GET /api/v1/mobile/lot-transformations`
|
||||||
|
- `POST /api/v1/mobile/lot-transformations`
|
||||||
|
- `GET /api/v1/mobile/lot-transformations/:id`
|
||||||
|
- `GET /api/v1/mobile/stock-adjustments/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/stock-adjustments`
|
||||||
|
- `POST /api/v1/mobile/stock-adjustments`
|
||||||
|
|
||||||
|
### Sales
|
||||||
|
|
||||||
|
Tujuan:
|
||||||
|
- lihat stok jual
|
||||||
|
- buat penjualan reguler
|
||||||
|
- buat penjualan JIT
|
||||||
|
- buat titip jual
|
||||||
|
- tutup transaksi
|
||||||
|
|
||||||
|
Endpoint:
|
||||||
|
- `GET /api/v1/mobile/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/dashboard`
|
||||||
|
- `GET /api/v1/mobile/lots`
|
||||||
|
- `GET /api/v1/mobile/lots/:id`
|
||||||
|
- `GET /api/v1/mobile/lots/scan?code=...`
|
||||||
|
- `GET /api/v1/mobile/sales-regular/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/sales-regular`
|
||||||
|
- `POST /api/v1/mobile/sales-regular`
|
||||||
|
- `GET /api/v1/mobile/sales-regular/:id`
|
||||||
|
- `POST /api/v1/mobile/sales-regular/:id/close`
|
||||||
|
- `GET /api/v1/mobile/sales-jit/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/sales-jit`
|
||||||
|
- `POST /api/v1/mobile/sales-jit`
|
||||||
|
- `GET /api/v1/mobile/sales-jit/:id`
|
||||||
|
- `POST /api/v1/mobile/sales-jit/:id/close`
|
||||||
|
- `GET /api/v1/mobile/consignments/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/consignments`
|
||||||
|
- `POST /api/v1/mobile/consignments`
|
||||||
|
- `GET /api/v1/mobile/consignments/:id`
|
||||||
|
- `POST /api/v1/mobile/consignments/lines/:lineId/close`
|
||||||
|
|
||||||
|
### Purchasing
|
||||||
|
|
||||||
|
Tujuan:
|
||||||
|
- buat draft pembelian
|
||||||
|
- edit / submit pembelian
|
||||||
|
- buat permintaan dana
|
||||||
|
- monitor analisis dan realisasi pembelian
|
||||||
|
|
||||||
|
Endpoint:
|
||||||
|
- `GET /api/v1/mobile/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/dashboard`
|
||||||
|
- `GET /api/v1/mobile/purchases`
|
||||||
|
- `POST /api/v1/mobile/purchases`
|
||||||
|
- `GET /api/v1/mobile/purchases/:id`
|
||||||
|
- `PUT /api/v1/mobile/purchases/:id`
|
||||||
|
- `POST /api/v1/mobile/purchases/:id/submit`
|
||||||
|
- `POST /api/v1/mobile/purchases/:id/cancel`
|
||||||
|
- `GET /api/v1/mobile/fund-requests/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/fund-requests`
|
||||||
|
- `POST /api/v1/mobile/fund-requests`
|
||||||
|
- `GET /api/v1/mobile/purchase-analyses`
|
||||||
|
- `GET /api/v1/mobile/purchase-analyses/:purchaseId`
|
||||||
|
- `GET /api/v1/mobile/purchase-realizations`
|
||||||
|
- `GET /api/v1/mobile/purchase-realizations/:purchaseId`
|
||||||
|
|
||||||
|
### Owner
|
||||||
|
|
||||||
|
Tujuan:
|
||||||
|
- monitoring dashboard
|
||||||
|
- lihat analisis pembelian
|
||||||
|
- lihat realisasi pembelian
|
||||||
|
- lihat status transaksi penting
|
||||||
|
|
||||||
|
Endpoint minimum:
|
||||||
|
- `GET /api/v1/mobile/bootstrap`
|
||||||
|
- `GET /api/v1/mobile/dashboard`
|
||||||
|
- `GET /api/v1/mobile/purchases`
|
||||||
|
- `GET /api/v1/mobile/purchases/:id`
|
||||||
|
- `GET /api/v1/mobile/fund-requests`
|
||||||
|
- `GET /api/v1/mobile/purchase-analyses`
|
||||||
|
- `GET /api/v1/mobile/purchase-analyses/:purchaseId`
|
||||||
|
- `GET /api/v1/mobile/purchase-realizations`
|
||||||
|
- `GET /api/v1/mobile/purchase-realizations/:purchaseId`
|
||||||
|
- `GET /api/v1/mobile/sales-regular`
|
||||||
|
- `GET /api/v1/mobile/sales-jit`
|
||||||
|
- `GET /api/v1/mobile/consignments`
|
||||||
|
- `GET /api/v1/mobile/washing`
|
||||||
|
|
||||||
|
## Layar Minimum Per Role
|
||||||
|
|
||||||
|
### Warehouse
|
||||||
|
|
||||||
|
Layar minimum:
|
||||||
|
1. Login
|
||||||
|
2. Dashboard ringkas
|
||||||
|
3. Scan lot
|
||||||
|
4. Detail lot
|
||||||
|
5. Daftar pembelian siap submit
|
||||||
|
6. Submit pembelian yang otomatis membuat receipt + lot
|
||||||
|
7. Penyesuaian stok
|
||||||
|
8. Daftar washing
|
||||||
|
9. Buat washing
|
||||||
|
10. Selesaikan washing
|
||||||
|
|
||||||
|
### QC
|
||||||
|
|
||||||
|
Layar minimum:
|
||||||
|
1. Login
|
||||||
|
2. Dashboard ringkas
|
||||||
|
3. Scan lot
|
||||||
|
4. Detail lot dan turunan lot
|
||||||
|
5. Daftar transformasi
|
||||||
|
6. Buat mixing / ubah grade
|
||||||
|
7. Daftar washing
|
||||||
|
8. Selesaikan washing
|
||||||
|
9. Penyesuaian stok QC
|
||||||
|
|
||||||
|
### Sales
|
||||||
|
|
||||||
|
Layar minimum:
|
||||||
|
1. Login
|
||||||
|
2. Dashboard ringkas
|
||||||
|
3. Stok siap jual
|
||||||
|
4. Scan lot
|
||||||
|
5. Buat penjualan reguler
|
||||||
|
6. Detail dan tutup penjualan reguler
|
||||||
|
7. Buat penjualan JIT
|
||||||
|
8. Detail dan tutup penjualan JIT
|
||||||
|
9. Buat titip jual
|
||||||
|
10. Detail dan tutup item titip jual
|
||||||
|
|
||||||
|
### Purchasing
|
||||||
|
|
||||||
|
Layar minimum:
|
||||||
|
1. Login
|
||||||
|
2. Dashboard ringkas
|
||||||
|
3. Daftar pembelian
|
||||||
|
4. Form draft pembelian
|
||||||
|
5. Detail pembelian
|
||||||
|
6. Submit pembelian
|
||||||
|
7. Daftar permintaan dana
|
||||||
|
8. Form permintaan dana
|
||||||
|
9. Daftar analisis pembelian
|
||||||
|
10. Daftar realisasi pembelian
|
||||||
|
|
||||||
|
### Owner
|
||||||
|
|
||||||
|
Layar minimum:
|
||||||
|
1. Login
|
||||||
|
2. Dashboard ringkas
|
||||||
|
3. Daftar pembelian
|
||||||
|
4. Detail pembelian
|
||||||
|
5. Daftar analisis pembelian
|
||||||
|
6. Detail analisis pembelian
|
||||||
|
7. Daftar realisasi pembelian
|
||||||
|
8. Detail realisasi pembelian
|
||||||
|
9. Daftar transaksi keluar
|
||||||
|
10. Daftar washing
|
||||||
|
|
||||||
|
## Scope Yang Sengaja Tidak Dibawa ke Mobile
|
||||||
|
|
||||||
|
- manajemen user
|
||||||
|
- pengaturan sistem
|
||||||
|
- audit trail penuh
|
||||||
|
- master data lengkap
|
||||||
|
- office buyout
|
||||||
|
- print label / dokumen
|
||||||
|
- laporan web yang kompleks
|
||||||
|
|
||||||
|
## Urutan Implementasi UI Mobile yang Disarankan
|
||||||
|
|
||||||
|
1. Warehouse
|
||||||
|
2. QC
|
||||||
|
3. Sales
|
||||||
|
4. Purchasing
|
||||||
|
5. Owner
|
||||||
|
|
||||||
|
Alasannya:
|
||||||
|
- Warehouse dan QC paling sering butuh scan dan aksi lapangan cepat
|
||||||
|
- Sales di urutan berikutnya karena butuh transaksi tapi tidak banyak input master
|
||||||
|
- Purchasing dan Owner lebih banyak monitoring dan persetujuan ringan
|
||||||
569
postman/abelbirdnest-mobile-api.postman_collection.json
Normal file
569
postman/abelbirdnest-mobile-api.postman_collection.json
Normal file
@ -0,0 +1,569 @@
|
|||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"name": "AbelBirdnest Mobile Operations API",
|
||||||
|
"description": "Collection Postman untuk integrasi mobile Warehouse, QC, Sales, Purchasing, dan Owner. Gunakan Login terlebih dahulu untuk mengisi {{sessionToken}}.",
|
||||||
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||||
|
},
|
||||||
|
"variable": [
|
||||||
|
{ "key": "baseUrl", "value": "http://localhost:3000/api/v1" },
|
||||||
|
{ "key": "sessionToken", "value": "" },
|
||||||
|
{ "key": "purchaseId", "value": "" },
|
||||||
|
{ "key": "receiptId", "value": "" },
|
||||||
|
{ "key": "lotId", "value": "" },
|
||||||
|
{ "key": "washingId", "value": "" },
|
||||||
|
{ "key": "regularSaleId", "value": "" },
|
||||||
|
{ "key": "jitSaleId", "value": "" },
|
||||||
|
{ "key": "consignmentId", "value": "" },
|
||||||
|
{ "key": "consignmentLineId", "value": "" },
|
||||||
|
{ "key": "purchaseAnalysisId", "value": "" },
|
||||||
|
{ "key": "purchaseRealizationId", "value": "" }
|
||||||
|
],
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{ "key": "token", "value": "{{sessionToken}}", "type": "string" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "1. Auth",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Login",
|
||||||
|
"event": [
|
||||||
|
{
|
||||||
|
"listen": "test",
|
||||||
|
"script": {
|
||||||
|
"type": "text/javascript",
|
||||||
|
"exec": [
|
||||||
|
"if (pm.response.code === 200) {",
|
||||||
|
" const json = pm.response.json();",
|
||||||
|
" const token = json?.data?.session_token;",
|
||||||
|
" const role = json?.data?.user?.role;",
|
||||||
|
" if (token) pm.collectionVariables.set('sessionToken', token);",
|
||||||
|
" if (role) pm.collectionVariables.set('userRole', role);",
|
||||||
|
"}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"identity\": \"admin\",\n \"password\": \"admin123\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/auth/login",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["auth", "login"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Session Me",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/auth/me",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["auth", "me"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2. Bootstrap & Dashboard",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Mobile Bootstrap",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/bootstrap",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "bootstrap"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mobile Dashboard",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/dashboard?locale=id",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "dashboard"],
|
||||||
|
"query": [
|
||||||
|
{ "key": "locale", "value": "id" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "3. Warehouse & QC",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Lot List",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/lots",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "lots"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lot Scan",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/lots/scan?code=LOT-EXAMPLE-001",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "lots", "scan"],
|
||||||
|
"query": [
|
||||||
|
{ "key": "code", "value": "LOT-EXAMPLE-001" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Lot Detail",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/lots/{{lotId}}",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "lots", "{{lotId}}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Receipt Bootstrap",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/receipts/bootstrap",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "receipts", "bootstrap"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Receipt List",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/receipts",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "receipts"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create Receipt",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"purchase_id\": \"{{purchaseId}}\",\n \"receipt_date\": \"2026-05-16\",\n \"notes\": \"Receipt dari mobile\",\n \"lines\": []\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/receipts",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "receipts"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Generate Lots From Receipt",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/receipts/{{receiptId}}/generate-lots",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "receipts", "{{receiptId}}", "generate-lots"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Stock Adjustment Bootstrap",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/stock-adjustments/bootstrap",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "stock-adjustments", "bootstrap"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create Stock Adjustment",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"lot_id\": \"{{lotId}}\",\n \"adjustment_reason_id\": \"\",\n \"adjustment_date\": \"2026-05-16\",\n \"qty_change\": -1,\n \"notes\": \"Mobile adjustment\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/stock-adjustments",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "stock-adjustments"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Washing Bootstrap",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/washing/bootstrap",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "washing", "bootstrap"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Washing List",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/washing",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "washing"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create Washing",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"lot_id\": \"{{lotId}}\",\n \"washing_place_id\": \"\",\n \"washing_cost\": 10000,\n \"duration_hours\": 24\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/washing",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "washing"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Complete Washing",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"after_qty\": 1,\n \"grade_id\": \"\",\n \"warehouse_id\": \"\",\n \"warehouse_location_id\": \"\"\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/washing/{{washingId}}/complete",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "washing", "{{washingId}}", "complete"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Transformation List",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/lot-transformations",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "lot-transformations"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Create Lot Transformation",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"transformation_type\": \"MIX\",\n \"transformation_date\": \"2026-05-16\",\n \"remainder_mode\": \"KEEP_SOURCE_GRADE\",\n \"processing_loss_mode\": \"SHRINKAGE\",\n \"notes\": \"Mobile transformation\",\n \"inputs\": [\n {\n \"source_lot_code\": \"LOT-EXAMPLE-001\",\n \"qty_used\": 1\n }\n ],\n \"outputs\": [\n {\n \"grade_id\": \"\",\n \"warehouse_id\": \"\",\n \"warehouse_location_id\": \"\",\n \"qty_produced\": 1\n }\n ]\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/lot-transformations",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "lot-transformations"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "4. Sales",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Regular Sales Bootstrap",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/sales-regular/bootstrap",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "sales-regular", "bootstrap"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Regular Sales List",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/sales-regular",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "sales-regular"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Regular Sale Detail",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/sales-regular/{{regularSaleId}}",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "sales-regular", "{{regularSaleId}}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Close Regular Sale",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"close_date\": \"2026-05-16\",\n \"lines\": []\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/sales-regular/{{regularSaleId}}/close",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "sales-regular", "{{regularSaleId}}", "close"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "JIT Sales Bootstrap",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/sales-jit/bootstrap",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "sales-jit", "bootstrap"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "JIT Sales List",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/sales-jit",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "sales-jit"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Close JIT Sale",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"close_date\": \"2026-05-16\",\n \"lines\": []\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/sales-jit/{{jitSaleId}}/close",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "sales-jit", "{{jitSaleId}}", "close"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Consignments Bootstrap",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/consignments/bootstrap",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "consignments", "bootstrap"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Consignments List",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/consignments",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "consignments"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Consignment Detail",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/consignments/{{consignmentId}}",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "consignments", "{{consignmentId}}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Close Consignment Line",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [
|
||||||
|
{ "key": "Content-Type", "value": "application/json" }
|
||||||
|
],
|
||||||
|
"body": {
|
||||||
|
"mode": "raw",
|
||||||
|
"raw": "{\n \"close_date\": \"2026-05-16\",\n \"qty_sold\": 1,\n \"qty_returned\": 0,\n \"selling_price\": 100000,\n \"sales_commission\": 0\n}"
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/consignments/lines/{{consignmentLineId}}/close",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "consignments", "lines", "{{consignmentLineId}}", "close"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "5. Purchasing & Owner",
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Purchases List",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/purchases",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "purchases"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Purchase Detail",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/purchases/{{purchaseId}}",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "purchases", "{{purchaseId}}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Submit Purchase",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/purchases/{{purchaseId}}/submit",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "purchases", "{{purchaseId}}", "submit"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fund Requests Bootstrap",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/fund-requests/bootstrap",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "fund-requests", "bootstrap"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fund Requests List",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/fund-requests",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "fund-requests"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Purchase Analyses",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/purchase-analyses",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "purchase-analyses"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Purchase Analysis Detail",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/purchase-analyses/{{purchaseAnalysisId}}",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "purchase-analyses", "{{purchaseAnalysisId}}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Purchase Realizations",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/purchase-realizations",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "purchase-realizations"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Purchase Realization Detail",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"url": {
|
||||||
|
"raw": "{{baseUrl}}/mobile/purchase-realizations/{{purchaseRealizationId}}",
|
||||||
|
"host": ["{{baseUrl}}"],
|
||||||
|
"path": ["mobile", "purchase-realizations", "{{purchaseRealizationId}}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
93
postman/abelbirdnest-mobile-local.postman_environment.json
Normal file
93
postman/abelbirdnest-mobile-local.postman_environment.json
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"id": "e5db42ce-7b3a-4d72-8df7-abelbirdnest-mobile-local",
|
||||||
|
"name": "AbelBirdnest Mobile Local",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"key": "baseUrl",
|
||||||
|
"value": "https://abelbirdnest.id/api/v1",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "sessionToken",
|
||||||
|
"value": "",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "defaultRedirectTo",
|
||||||
|
"value": "",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "userRole",
|
||||||
|
"value": "",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "scanCode",
|
||||||
|
"value": "LOT-260501-MIX-001",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "lotId",
|
||||||
|
"value": "1",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "transformationId",
|
||||||
|
"value": "1",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "itemTypeId",
|
||||||
|
"value": "1",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "itemGradeId",
|
||||||
|
"value": "1",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "targetItemGradeId",
|
||||||
|
"value": "2",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "warehouseId",
|
||||||
|
"value": "1",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "warehouseLocationId",
|
||||||
|
"value": "1",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "sourceLotCode1",
|
||||||
|
"value": "LOT-260501-MIX-001",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "sourceLotCode2",
|
||||||
|
"value": "LOT-260501-MIX-002",
|
||||||
|
"type": "text",
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"_postman_variable_scope": "environment",
|
||||||
|
"_postman_exported_at": "2026-05-02T06:05:00+07:00",
|
||||||
|
"_postman_exported_using": "Codex GPT-5"
|
||||||
|
}
|
||||||
18
settings.gradle.kts
Normal file
18
settings.gradle.kts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "Abelbirdnest Stock"
|
||||||
|
include(":app")
|
||||||
Reference in New Issue
Block a user