From cdfea93d547d54a8f4d618cd41a985367fdfcba2 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 12:11:44 +0900 Subject: [PATCH 01/25] =?UTF-8?q?[feat]=20#3=20=EC=95=88=EB=93=9C=EB=A1=9C?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=20=ED=99=98=EA=B2=BD=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 4 ++++ app/src/main/AndroidManifest.xml | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 48eadd9..8529453 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -47,4 +47,8 @@ dependencies { // ML Kit Barcode Scanning implementation 'com.google.mlkit:barcode-scanning:17.2.0' + + // 안드로이드에서 API 호출 + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d19d1b4..ca6e2e9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,10 +3,12 @@ xmlns:tools="http://schemas.android.com/tools"> + From 9421c274e400c10027d57fdae75606c8240e6e73 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 12:12:43 +0900 Subject: [PATCH 02/25] =?UTF-8?q?[chore]=20#3=20=ED=8F=B4=EB=8D=94=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/chain_g/FacManagerMainActivity.java | 2 ++ .../chain_g/{ => auth/activity}/FindAccountActivity.java | 5 +++-- app/src/main/res/layout/activity_find_account.xml | 2 +- app/src/main/res/layout/activity_login.xml | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) rename app/src/main/java/com/example/chain_g/{ => auth/activity}/FindAccountActivity.java (93%) diff --git a/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java b/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java index 1fd86cf..b012ee9 100644 --- a/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java +++ b/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java @@ -6,6 +6,8 @@ import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; +import com.example.chain_g.auth.activity.LoginActivity; + public class FacManagerMainActivity extends AppCompatActivity { @Override diff --git a/app/src/main/java/com/example/chain_g/FindAccountActivity.java b/app/src/main/java/com/example/chain_g/auth/activity/FindAccountActivity.java similarity index 93% rename from app/src/main/java/com/example/chain_g/FindAccountActivity.java rename to app/src/main/java/com/example/chain_g/auth/activity/FindAccountActivity.java index 3220556..cffab7a 100644 --- a/app/src/main/java/com/example/chain_g/FindAccountActivity.java +++ b/app/src/main/java/com/example/chain_g/auth/activity/FindAccountActivity.java @@ -1,6 +1,5 @@ -package com.example.chain_g; +package com.example.chain_g.auth.activity; -import android.content.Intent; import android.os.Bundle; import android.widget.TextView; @@ -10,6 +9,8 @@ import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import com.example.chain_g.R; + public class FindAccountActivity extends AppCompatActivity { @Override diff --git a/app/src/main/res/layout/activity_find_account.xml b/app/src/main/res/layout/activity_find_account.xml index bb21983..b0d0195 100644 --- a/app/src/main/res/layout/activity_find_account.xml +++ b/app/src/main/res/layout/activity_find_account.xml @@ -8,7 +8,7 @@ android:gravity="center" android:orientation="vertical" android:background="#FFFFFF" - tools:context=".FindAccountActivity"> + tools:context=".auth.activity.FindAccountActivity"> + tools:context=".auth.activity.LoginActivity"> Date: Fri, 6 Mar 2026 12:13:11 +0900 Subject: [PATCH 03/25] =?UTF-8?q?[feat]=20#3=20RetrofitClient=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/chain_g/common/ApiResponse.java | 12 ++++++++++++ .../chain_g/common/RetrofitClient.java | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 app/src/main/java/com/example/chain_g/common/ApiResponse.java create mode 100644 app/src/main/java/com/example/chain_g/common/RetrofitClient.java diff --git a/app/src/main/java/com/example/chain_g/common/ApiResponse.java b/app/src/main/java/com/example/chain_g/common/ApiResponse.java new file mode 100644 index 0000000..574dac9 --- /dev/null +++ b/app/src/main/java/com/example/chain_g/common/ApiResponse.java @@ -0,0 +1,12 @@ +package com.example.chain_g.common; + +public class ApiResponse { + + private String status; + private String message; + private T data; + + public String getStatus() { return status; } + public String getMessage() { return message; } + public T getData() { return data; } +} diff --git a/app/src/main/java/com/example/chain_g/common/RetrofitClient.java b/app/src/main/java/com/example/chain_g/common/RetrofitClient.java new file mode 100644 index 0000000..c34d1af --- /dev/null +++ b/app/src/main/java/com/example/chain_g/common/RetrofitClient.java @@ -0,0 +1,19 @@ +package com.example.chain_g.common; + +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class RetrofitClient { + private static final String BASE_URL = "http://10.0.2.2:8080/"; + private static Retrofit retrofit = null; + + public static ApiService getApiService() { + if (retrofit == null) { + retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + } + return retrofit.create(ApiService.class); + } +} From cfb985ecd007720a757f4a88de5cccc1631b535e Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 12:13:21 +0900 Subject: [PATCH 04/25] =?UTF-8?q?[feat]=20#3=20TokenManager=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chain_g/auth/jwt/TokenManager.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java diff --git a/app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java b/app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java new file mode 100644 index 0000000..1f8c531 --- /dev/null +++ b/app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java @@ -0,0 +1,20 @@ +package com.example.chain_g.auth.jwt; + +import android.content.Context; +import android.content.SharedPreferences; + +public class TokenManager { + + private static final String PREF_NAME = "auth_prefs"; + private static final String KEY_ACCESS_TOKEN = "access_token"; + + public static void saveToken(Context context, String token) { + SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + prefs.edit().putString(KEY_ACCESS_TOKEN, token).apply(); + } + + public static String getToken(Context context) { + SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + return prefs.getString(KEY_ACCESS_TOKEN, null); + } +} From 1304a608cc0d70707fbb6ad98af53527ea8ff810 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 12:13:31 +0900 Subject: [PATCH 05/25] =?UTF-8?q?[feat]=20#3=20ApiService=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/chain_g/common/ApiService.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 app/src/main/java/com/example/chain_g/common/ApiService.java diff --git a/app/src/main/java/com/example/chain_g/common/ApiService.java b/app/src/main/java/com/example/chain_g/common/ApiService.java new file mode 100644 index 0000000..1ee31f3 --- /dev/null +++ b/app/src/main/java/com/example/chain_g/common/ApiService.java @@ -0,0 +1,15 @@ +package com.example.chain_g.common; + +import com.example.chain_g.auth.dto.request.LoginRequest; +import com.example.chain_g.auth.dto.response.LoginResponse; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.POST; + +public interface ApiService { + + // 로그인 + @POST("/api/v1/auth/login") + Call> login(@Body LoginRequest request); +} From 4d77b42716f67b791cf4ded0992aae759b67b2d9 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 12:14:18 +0900 Subject: [PATCH 06/25] =?UTF-8?q?[chore]=20#3=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/chain_g/LoginActivity.java | 51 ------------------- 1 file changed, 51 deletions(-) delete mode 100644 app/src/main/java/com/example/chain_g/LoginActivity.java diff --git a/app/src/main/java/com/example/chain_g/LoginActivity.java b/app/src/main/java/com/example/chain_g/LoginActivity.java deleted file mode 100644 index 155aeb2..0000000 --- a/app/src/main/java/com/example/chain_g/LoginActivity.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.example.chain_g; - -import android.content.Intent; -import android.os.Bundle; -import android.widget.EditText; -import android.widget.TextView; - -import androidx.activity.EdgeToEdge; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; - -public class LoginActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - setContentView(R.layout.activity_login); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); - - TextView loginButton = findViewById(R.id.login_btn); - TextView findAccountButton = findViewById(R.id.find_account_btn); - - EditText id = findViewById(R.id.id); - EditText password = findViewById(R.id.password); - - loginButton.setOnClickListener(v -> { - - String inputId = id.getText().toString().trim(); - - if(inputId.equals("fac")) { - Intent intent = new Intent(LoginActivity.this, FacManagerMainActivity.class); - startActivity(intent); - } else if (inputId.equals("fran")) { - Intent intent = new Intent(LoginActivity.this, FranManagerMainActivity.class); - startActivity(intent); - } - }); - - findAccountButton.setOnClickListener(v -> { - Intent intent = new Intent(LoginActivity.this, FindAccountActivity.class); - startActivity(intent); - }); - } -} \ No newline at end of file From d7dec7b34663cc831967fe3a7246d72a015c5be8 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 12:15:50 +0900 Subject: [PATCH 07/25] =?UTF-8?q?[feat]=20#3=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20dto=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/dto/request/LoginRequest.java | 20 ++++++++++++++++++ .../auth/dto/response/LoginResponse.java | 21 +++++++++++++++++++ .../example/chain_g/common/ApiService.java | 4 ++++ 3 files changed, 45 insertions(+) create mode 100644 app/src/main/java/com/example/chain_g/auth/dto/request/LoginRequest.java create mode 100644 app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java diff --git a/app/src/main/java/com/example/chain_g/auth/dto/request/LoginRequest.java b/app/src/main/java/com/example/chain_g/auth/dto/request/LoginRequest.java new file mode 100644 index 0000000..d35630d --- /dev/null +++ b/app/src/main/java/com/example/chain_g/auth/dto/request/LoginRequest.java @@ -0,0 +1,20 @@ +package com.example.chain_g.auth.dto.request; + +public class LoginRequest { + + private final String loginId; + private final String password; + + public LoginRequest(String loginId, String password) { + this.loginId = loginId; + this.password = password; + } + + public String getLoginId() { + return loginId; + } + + public String getPassword() { + return password; + } +} diff --git a/app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java b/app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java new file mode 100644 index 0000000..0198cb0 --- /dev/null +++ b/app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java @@ -0,0 +1,21 @@ +package com.example.chain_g.auth.dto.response; + +public class LoginResponse { + + private final String accessToken; + + private final String refreshToken; + + public LoginResponse(String accessToken, String refreshToken) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + } + + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } +} diff --git a/app/src/main/java/com/example/chain_g/common/ApiService.java b/app/src/main/java/com/example/chain_g/common/ApiService.java index 1ee31f3..42af800 100644 --- a/app/src/main/java/com/example/chain_g/common/ApiService.java +++ b/app/src/main/java/com/example/chain_g/common/ApiService.java @@ -12,4 +12,8 @@ public interface ApiService { // 로그인 @POST("/api/v1/auth/login") Call> login(@Body LoginRequest request); + + // 비밀번호 재설정 + + // 로그아웃 } From 2bffcc6f5efcd3d4d4c25fae3ad1fef1c18649b4 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 12:18:24 +0900 Subject: [PATCH 08/25] =?UTF-8?q?[feat]=20#3=20LoginActivity=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chain_g/auth/activity/LoginActivity.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java diff --git a/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java b/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java new file mode 100644 index 0000000..cd92088 --- /dev/null +++ b/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java @@ -0,0 +1,94 @@ +package com.example.chain_g.auth.activity; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.activity.EdgeToEdge; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.example.chain_g.FacManagerMainActivity; +import com.example.chain_g.FranManagerMainActivity; +import com.example.chain_g.R; +import com.example.chain_g.auth.dto.request.LoginRequest; +import com.example.chain_g.auth.dto.response.LoginResponse; +import com.example.chain_g.common.ApiResponse; +import com.example.chain_g.common.RetrofitClient; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class LoginActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_login); + + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + TextView loginButton = findViewById(R.id.login_btn); + TextView findAccountButton = findViewById(R.id.find_account_btn); + EditText idField = findViewById(R.id.id); + EditText passwordField = findViewById(R.id.password); + + loginButton.setOnClickListener(v -> { + String inputId = idField.getText().toString().trim(); + String inputPw = passwordField.getText().toString().trim(); + + if (inputId.isEmpty() || inputPw.isEmpty()) { + Toast.makeText(this, "아이디와 비밀번호를 입력해주세요.", Toast.LENGTH_SHORT).show(); + return; + } + + LoginRequest loginRequest = new LoginRequest(inputId, inputPw); + + RetrofitClient.getApiService().login(loginRequest).enqueue(new Callback>() { + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + if (response.isSuccessful() && response.body() != null) { + LoginResponse loginData = response.body().getData(); + String accessToken = loginData.getAccessToken(); + + Log.d("LOGIN_API", "로그인 성공! 토큰: " + accessToken); + + if (inputId.contains("fac")) { + startActivity(new Intent(LoginActivity.this, FacManagerMainActivity.class)); + } else { + startActivity(new Intent(LoginActivity.this, FranManagerMainActivity.class)); + } + finish(); + + } else { + Log.e("LOGIN_API", "실패 코드: " + response.code()); + Toast.makeText(LoginActivity.this, "로그인 정보가 올바르지 않습니다.", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + Log.e("LOGIN_API", "네트워크 에러: " + t.getMessage()); + Toast.makeText(LoginActivity.this, "서버 연결에 실패했습니다.", Toast.LENGTH_SHORT).show(); + } + }); + }); + + findAccountButton.setOnClickListener(v -> { + Intent intent = new Intent(LoginActivity.this, FindAccountActivity.class); + startActivity(intent); + }); + } +} \ No newline at end of file From dd5caafe37c9cd110988f37879a3639c391dba54 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 14:42:43 +0900 Subject: [PATCH 09/25] =?UTF-8?q?[feat]=20#3=20LoginActivity=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EC=A0=80=EC=9E=A5=20=EB=B0=8F=20=EA=B6=8C=ED=95=9C?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chain_g/auth/activity/LoginActivity.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java b/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java index cd92088..1af5c2d 100644 --- a/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java +++ b/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java @@ -19,6 +19,8 @@ import com.example.chain_g.R; import com.example.chain_g.auth.dto.request.LoginRequest; import com.example.chain_g.auth.dto.response.LoginResponse; +import com.example.chain_g.auth.enums.UserRole; +import com.example.chain_g.auth.jwt.TokenManager; import com.example.chain_g.common.ApiResponse; import com.example.chain_g.common.RetrofitClient; @@ -30,6 +32,7 @@ public class LoginActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_login); @@ -61,15 +64,26 @@ protected void onCreate(Bundle savedInstanceState) { public void onResponse(@NonNull Call> call, @NonNull Response> response) { if (response.isSuccessful() && response.body() != null) { LoginResponse loginData = response.body().getData(); + String accessToken = loginData.getAccessToken(); + String refreshToken = loginData.getRefreshToken(); + UserRole userRole = loginData.getRole(); - Log.d("LOGIN_API", "로그인 성공! 토큰: " + accessToken); + TokenManager.saveTokens(LoginActivity.this, accessToken, refreshToken); - if (inputId.contains("fac")) { - startActivity(new Intent(LoginActivity.this, FacManagerMainActivity.class)); + Log.d("LOGIN_API", "로그인 성공!"); + + Intent intent; + if (userRole == UserRole.FACTORY) { + intent = new Intent(LoginActivity.this, FacManagerMainActivity.class); + } else if (userRole == UserRole.FRANCHISE) { + intent = new Intent(LoginActivity.this, FranManagerMainActivity.class); } else { - startActivity(new Intent(LoginActivity.this, FranManagerMainActivity.class)); + Toast.makeText(LoginActivity.this, "접근 권한이 없습니다.", Toast.LENGTH_SHORT).show(); + return; } + + startActivity(intent); finish(); } else { From f6c5aaa89a811c44deed836266b5922362b6f079 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 14:43:14 +0900 Subject: [PATCH 10/25] =?UTF-8?q?[feat]=20#3=20UserRole=20enum=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/example/chain_g/auth/enums/UserRole.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 app/src/main/java/com/example/chain_g/auth/enums/UserRole.java diff --git a/app/src/main/java/com/example/chain_g/auth/enums/UserRole.java b/app/src/main/java/com/example/chain_g/auth/enums/UserRole.java new file mode 100644 index 0000000..377edac --- /dev/null +++ b/app/src/main/java/com/example/chain_g/auth/enums/UserRole.java @@ -0,0 +1,7 @@ +package com.example.chain_g.auth.enums; + +public enum UserRole { + + FRANCHISE, + FACTORY; +} From 7830faf39320f0b0cbc8a6d5543b6076d38980cc Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 14:43:39 +0900 Subject: [PATCH 11/25] =?UTF-8?q?[feat]=20#3=20LoginResponse=EC=97=90=20Us?= =?UTF-8?q?erRole=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chain_g/auth/dto/response/LoginResponse.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java b/app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java index 0198cb0..a29ff5d 100644 --- a/app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java +++ b/app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java @@ -1,14 +1,19 @@ package com.example.chain_g.auth.dto.response; +import com.example.chain_g.auth.enums.UserRole; + public class LoginResponse { private final String accessToken; private final String refreshToken; - public LoginResponse(String accessToken, String refreshToken) { + private final UserRole role; + + public LoginResponse(String accessToken, String refreshToken, UserRole role) { this.accessToken = accessToken; this.refreshToken = refreshToken; + this.role = role; } public String getAccessToken() { @@ -18,4 +23,8 @@ public String getAccessToken() { public String getRefreshToken() { return refreshToken; } + + public UserRole getRole() { + return role; + } } From f8c03056f9e2c292e63b0033898cc4a0b0c6a2e2 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 15:37:02 +0900 Subject: [PATCH 12/25] =?UTF-8?q?[feat]=20#3=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chain_g/FacManagerMainActivity.java | 28 +++++++++-------- .../chain_g/FranManagerMainActivity.java | 30 ++++++++++++------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java b/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java index b012ee9..c237ef9 100644 --- a/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java +++ b/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java @@ -2,11 +2,14 @@ import android.content.Intent; import android.os.Bundle; -import android.widget.LinearLayout; // ⭐ 로그아웃 레이아웃을 위해 추가! +import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; + import androidx.appcompat.app.AppCompatActivity; import com.example.chain_g.auth.activity.LoginActivity; +import com.example.chain_g.auth.jwt.TokenManager; public class FacManagerMainActivity extends AppCompatActivity { @@ -15,35 +18,34 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fac_manager_main); - // 1. 버튼 및 레이아웃 연결하기 - TextView btnInput = findViewById(R.id.btn_input); // 입고 버튼 - TextView btnOutput = findViewById(R.id.btn_output); // 출고 버튼 - LinearLayout layoutLogout = findViewById(R.id.layout_logout); // ⭐ 로그아웃 레이아웃 + TextView btnInput = findViewById(R.id.btn_input); + TextView btnOutput = findViewById(R.id.btn_output); + LinearLayout layoutLogout = findViewById(R.id.layout_logout); - // 2. [입고] 버튼 눌렀을 때 + // 입고 btnInput.setOnClickListener(v -> { Intent intent = new Intent(FacManagerMainActivity.this, BoxScanActivity.class); intent.putExtra("mode", "IN"); startActivity(intent); }); - // 3. [출고] 버튼 눌렀을 때 + // 출고 btnOutput.setOnClickListener(v -> { Intent intent = new Intent(FacManagerMainActivity.this, BoxScanActivity.class); intent.putExtra("mode", "OUT"); startActivity(intent); }); - // 4. [로그아웃] 영역 눌렀을 때 (아이콘+글자 포함) + // 로그아웃 layoutLogout.setOnClickListener(v -> { - // ⭐ 로그인 화면(LoginActivity)으로 이동! (파일 이름이 다르면 수정해줘!) - Intent intent = new Intent(FacManagerMainActivity.this, LoginActivity.class); - // ⭐ 중요! 로그인 화면으로 갈 때 이전 화면 기록(스택)을 다 지워야 해! - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + TokenManager.clearTokens(this); + Intent intent = new Intent(this, LoginActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); - finish(); // 현재 메인 화면 닫기! + + Toast.makeText(this, "로그아웃 되었습니다.", Toast.LENGTH_SHORT).show(); }); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java b/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java index b712f5e..3097ea7 100644 --- a/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java +++ b/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java @@ -4,12 +4,17 @@ import android.os.Bundle; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; + import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import com.example.chain_g.auth.activity.LoginActivity; +import com.example.chain_g.auth.jwt.TokenManager; + public class FranManagerMainActivity extends AppCompatActivity { @Override @@ -18,36 +23,39 @@ protected void onCreate(Bundle savedInstanceState) { EdgeToEdge.enable(this); setContentView(R.layout.activity_fran_manager_main); - // 상단 바 겹침 방지 (기본 세팅!) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); - // 1. 버튼들 연결하기 (XML ID 확인!) - TextView btnInput = findViewById(R.id.btn_input); // 입고 - TextView btnSell = findViewById(R.id.btn_sell); // 판매 - LinearLayout layoutLogout = findViewById(R.id.layout_logout); // 로그아웃 영역 + TextView btnInput = findViewById(R.id.btn_input); + TextView btnSell = findViewById(R.id.btn_sell); + LinearLayout layoutLogout = findViewById(R.id.layout_logout); - // 2. [입고] 버튼 클릭 -> BoxScanActivity로 이동 + // 2입고 btnInput.setOnClickListener(v -> { Intent intent = new Intent(FranManagerMainActivity.this, BoxScanActivity.class); - // 가맹점주 모드라는 걸 알려주기 위해 데이터를 보낼 수도 있어! intent.putExtra("type", "FRAN_IN"); startActivity(intent); }); - // 3. [판매] 버튼 클릭 -> SaleScanActivity로 이동 + // 판매 btnSell.setOnClickListener(v -> { Intent intent = new Intent(FranManagerMainActivity.this, SaleScanActivity.class); startActivity(intent); }); - // 4. [로그아웃] 버튼 클릭 -> 로그인 화면으로 돌아가거나 앱 종료 + // 로그아웃 layoutLogout.setOnClickListener(v -> { - // 현재 화면을 닫아서 이전(로그인) 화면으로 보내기! - finish(); + + TokenManager.clearTokens(this); + + Intent intent = new Intent(this, LoginActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + + Toast.makeText(this, "로그아웃 되었습니다.", Toast.LENGTH_SHORT).show(); }); } } \ No newline at end of file From a46e9d36854472af7e6669dadd721613a8efa7df Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 15:37:48 +0900 Subject: [PATCH 13/25] =?UTF-8?q?[feat]=20#3=20=EC=BB=AC=EB=9F=BC=EB=AA=85?= =?UTF-8?q?=20userRole=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/chain_g/auth/activity/LoginActivity.java | 2 +- .../chain_g/auth/dto/response/LoginResponse.java | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java b/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java index 1af5c2d..486e785 100644 --- a/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java +++ b/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java @@ -67,7 +67,7 @@ public void onResponse(@NonNull Call> call, @NonNull String accessToken = loginData.getAccessToken(); String refreshToken = loginData.getRefreshToken(); - UserRole userRole = loginData.getRole(); + UserRole userRole = loginData.getUserRole(); TokenManager.saveTokens(LoginActivity.this, accessToken, refreshToken); diff --git a/app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java b/app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java index a29ff5d..4372ac3 100644 --- a/app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java +++ b/app/src/main/java/com/example/chain_g/auth/dto/response/LoginResponse.java @@ -8,12 +8,12 @@ public class LoginResponse { private final String refreshToken; - private final UserRole role; + private final UserRole userRole; - public LoginResponse(String accessToken, String refreshToken, UserRole role) { + public LoginResponse(String accessToken, String refreshToken, UserRole userRole) { this.accessToken = accessToken; this.refreshToken = refreshToken; - this.role = role; + this.userRole = userRole; } public String getAccessToken() { @@ -24,7 +24,7 @@ public String getRefreshToken() { return refreshToken; } - public UserRole getRole() { - return role; + public UserRole getUserRole() { + return userRole; } } From e53c3b768ffe517feef006c3c563eb8a7013d675 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 15:38:05 +0900 Subject: [PATCH 14/25] =?UTF-8?q?[feat]=20#3=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=EC=9D=84=20=EC=9C=84=ED=95=9C=20clearTokens=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/chain_g/auth/jwt/TokenManager.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java b/app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java index 1f8c531..3fa3c2d 100644 --- a/app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java +++ b/app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java @@ -7,14 +7,23 @@ public class TokenManager { private static final String PREF_NAME = "auth_prefs"; private static final String KEY_ACCESS_TOKEN = "access_token"; + private static final String KEY_REFRESH_TOKEN = "refresh_token"; - public static void saveToken(Context context, String token) { + public static void saveTokens(Context context, String access, String refresh) { SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); - prefs.edit().putString(KEY_ACCESS_TOKEN, token).apply(); + prefs.edit() + .putString(KEY_ACCESS_TOKEN, access) + .putString(KEY_REFRESH_TOKEN, refresh) + .apply(); } - public static String getToken(Context context) { + public static String getRefreshToken(Context context) { SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); - return prefs.getString(KEY_ACCESS_TOKEN, null); + return prefs.getString(KEY_REFRESH_TOKEN, null); + } + + public static void clearTokens(Context context) { + SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + prefs.edit().remove(KEY_ACCESS_TOKEN).apply(); } } From c5f04305d57202114d367a41c1b9b0a00c036d71 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 15:52:03 +0900 Subject: [PATCH 15/25] =?UTF-8?q?[refactor]=20#3=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=8B=9C=EC=97=90=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=95=84=EC=9B=83=20api=20=ED=98=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chain_g/FacManagerMainActivity.java | 33 ++++++++++++++++--- .../chain_g/FranManagerMainActivity.java | 33 ++++++++++++++++--- 2 files changed, 56 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java b/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java index c237ef9..065ed4a 100644 --- a/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java +++ b/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java @@ -6,10 +6,17 @@ import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import com.example.chain_g.auth.activity.LoginActivity; import com.example.chain_g.auth.jwt.TokenManager; +import com.example.chain_g.common.ApiResponse; +import com.example.chain_g.common.RetrofitClient; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; public class FacManagerMainActivity extends AppCompatActivity { @@ -39,13 +46,29 @@ protected void onCreate(Bundle savedInstanceState) { // 로그아웃 layoutLogout.setOnClickListener(v -> { - TokenManager.clearTokens(this); + String refreshToken = TokenManager.getRefreshToken(this); + String authHeader = "Bearer " + refreshToken; - Intent intent = new Intent(this, LoginActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); + RetrofitClient.getApiService().logout(authHeader).enqueue(new Callback>() { + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + performLocalLogout(); + } - Toast.makeText(this, "로그아웃 되었습니다.", Toast.LENGTH_SHORT).show(); + @Override + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + performLocalLogout(); + } + }); }); } + + private void performLocalLogout() { + TokenManager.clearTokens(this); + Intent intent = new Intent(this, LoginActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + finish(); + Toast.makeText(this, "로그아웃 되었습니다.", Toast.LENGTH_SHORT).show(); + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java b/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java index 3097ea7..0143340 100644 --- a/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java +++ b/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java @@ -7,6 +7,7 @@ import android.widget.Toast; import androidx.activity.EdgeToEdge; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; @@ -14,6 +15,12 @@ import com.example.chain_g.auth.activity.LoginActivity; import com.example.chain_g.auth.jwt.TokenManager; +import com.example.chain_g.common.ApiResponse; +import com.example.chain_g.common.RetrofitClient; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; public class FranManagerMainActivity extends AppCompatActivity { @@ -49,13 +56,29 @@ protected void onCreate(Bundle savedInstanceState) { // 로그아웃 layoutLogout.setOnClickListener(v -> { - TokenManager.clearTokens(this); + String refreshToken = TokenManager.getRefreshToken(this); + String authHeader = "Bearer " + refreshToken; - Intent intent = new Intent(this, LoginActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); + RetrofitClient.getApiService().logout(authHeader).enqueue(new Callback>() { + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + performLocalLogout(); + } - Toast.makeText(this, "로그아웃 되었습니다.", Toast.LENGTH_SHORT).show(); + @Override + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + performLocalLogout(); + } + }); }); } + + private void performLocalLogout() { + TokenManager.clearTokens(this); + Intent intent = new Intent(this, LoginActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + finish(); + Toast.makeText(this, "로그아웃 되었습니다.", Toast.LENGTH_SHORT).show(); + } } \ No newline at end of file From d1d04adc376634f59c2cf942faf38bbdae91003f Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 15:52:37 +0900 Subject: [PATCH 16/25] =?UTF-8?q?[feat]=20#3=20ApiService=EC=97=90=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/com/example/chain_g/common/ApiService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/chain_g/common/ApiService.java b/app/src/main/java/com/example/chain_g/common/ApiService.java index 42af800..21100ff 100644 --- a/app/src/main/java/com/example/chain_g/common/ApiService.java +++ b/app/src/main/java/com/example/chain_g/common/ApiService.java @@ -5,6 +5,7 @@ import retrofit2.Call; import retrofit2.http.Body; +import retrofit2.http.Header; import retrofit2.http.POST; public interface ApiService { @@ -13,7 +14,7 @@ public interface ApiService { @POST("/api/v1/auth/login") Call> login(@Body LoginRequest request); - // 비밀번호 재설정 - // 로그아웃 + @POST("/api/v1/auth/logout") + Call> logout(@Header("Authorization") String token); } From 71d7600a2e9db3f7bd4ff7513a98f877b2cde9a1 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 16:39:44 +0900 Subject: [PATCH 17/25] =?UTF-8?q?[feat]=20#3=20ResetPasswordRequest=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/ResetPasswordRequest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 app/src/main/java/com/example/chain_g/auth/dto/request/ResetPasswordRequest.java diff --git a/app/src/main/java/com/example/chain_g/auth/dto/request/ResetPasswordRequest.java b/app/src/main/java/com/example/chain_g/auth/dto/request/ResetPasswordRequest.java new file mode 100644 index 0000000..a03f766 --- /dev/null +++ b/app/src/main/java/com/example/chain_g/auth/dto/request/ResetPasswordRequest.java @@ -0,0 +1,20 @@ +package com.example.chain_g.auth.dto.request; + +public class ResetPasswordRequest { + + private final String loginId; + public final String email; + + public ResetPasswordRequest(String loginId, String email) { + this.loginId = loginId; + this.email = email; + } + + public String getLoginId() { + return loginId; + } + + public String getEmail() { + return email; + } +} From f019df152ac0cfe4d1f19fd09290b1e4593dbd83 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 16:40:15 +0900 Subject: [PATCH 18/25] =?UTF-8?q?[feat]=20#3=20ApiService=EC=97=90=20?= =?UTF-8?q?=EB=B9=84=EB=B0=80=EB=B2=88=ED=98=B8=20=EC=9E=AC=EC=84=A4?= =?UTF-8?q?=EC=A0=95=EA=B3=BC=20=ED=86=A0=ED=81=B0=20=EC=9E=AC=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/example/chain_g/common/ApiService.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/java/com/example/chain_g/common/ApiService.java b/app/src/main/java/com/example/chain_g/common/ApiService.java index 21100ff..8440134 100644 --- a/app/src/main/java/com/example/chain_g/common/ApiService.java +++ b/app/src/main/java/com/example/chain_g/common/ApiService.java @@ -1,6 +1,7 @@ package com.example.chain_g.common; import com.example.chain_g.auth.dto.request.LoginRequest; +import com.example.chain_g.auth.dto.request.ResetPasswordRequest; import com.example.chain_g.auth.dto.response.LoginResponse; import retrofit2.Call; @@ -14,6 +15,14 @@ public interface ApiService { @POST("/api/v1/auth/login") Call> login(@Body LoginRequest request); + // 비밀번호 재설정 + @POST("/api/v1/auth/reset-password") + Call> resetPassword(@Body ResetPasswordRequest request); + + // 토큰 재발급 + @POST("/api/v1/auth/reissue") + Call> reissue(@Header("Authorization-Refresh") String refreshToken); + // 로그아웃 @POST("/api/v1/auth/logout") Call> logout(@Header("Authorization") String token); From 82658ee4a09b5068928583ae1ded83ab94100076 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 16:41:33 +0900 Subject: [PATCH 19/25] =?UTF-8?q?[refactor]=20#3=20reissue=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chain_g/FacManagerMainActivity.java | 2 +- .../chain_g/FranManagerMainActivity.java | 2 +- .../chain_g/auth/activity/LoginActivity.java | 2 +- .../chain_g/auth/jwt/TokenManager.java | 10 ++- .../chain_g/common/RetrofitClient.java | 69 ++++++++++++++++++- 5 files changed, 79 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java b/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java index 065ed4a..ab301e0 100644 --- a/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java +++ b/app/src/main/java/com/example/chain_g/FacManagerMainActivity.java @@ -49,7 +49,7 @@ protected void onCreate(Bundle savedInstanceState) { String refreshToken = TokenManager.getRefreshToken(this); String authHeader = "Bearer " + refreshToken; - RetrofitClient.getApiService().logout(authHeader).enqueue(new Callback>() { + RetrofitClient.getApiService(this).logout(authHeader).enqueue(new Callback>() { @Override public void onResponse(@NonNull Call> call, @NonNull Response> response) { performLocalLogout(); diff --git a/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java b/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java index 0143340..1070707 100644 --- a/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java +++ b/app/src/main/java/com/example/chain_g/FranManagerMainActivity.java @@ -59,7 +59,7 @@ protected void onCreate(Bundle savedInstanceState) { String refreshToken = TokenManager.getRefreshToken(this); String authHeader = "Bearer " + refreshToken; - RetrofitClient.getApiService().logout(authHeader).enqueue(new Callback>() { + RetrofitClient.getApiService(this).logout(authHeader).enqueue(new Callback>() { @Override public void onResponse(@NonNull Call> call, @NonNull Response> response) { performLocalLogout(); diff --git a/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java b/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java index 486e785..bda40a4 100644 --- a/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java +++ b/app/src/main/java/com/example/chain_g/auth/activity/LoginActivity.java @@ -59,7 +59,7 @@ protected void onCreate(Bundle savedInstanceState) { LoginRequest loginRequest = new LoginRequest(inputId, inputPw); - RetrofitClient.getApiService().login(loginRequest).enqueue(new Callback>() { + RetrofitClient.getApiService(LoginActivity.this).login(loginRequest).enqueue(new Callback>() { @Override public void onResponse(@NonNull Call> call, @NonNull Response> response) { if (response.isSuccessful() && response.body() != null) { diff --git a/app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java b/app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java index 3fa3c2d..2d9f38c 100644 --- a/app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java +++ b/app/src/main/java/com/example/chain_g/auth/jwt/TokenManager.java @@ -17,6 +17,11 @@ public static void saveTokens(Context context, String access, String refresh) { .apply(); } + public static String getAccessToken(Context context) { + SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); + return prefs.getString(KEY_ACCESS_TOKEN, null); + } + public static String getRefreshToken(Context context) { SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); return prefs.getString(KEY_REFRESH_TOKEN, null); @@ -24,6 +29,9 @@ public static String getRefreshToken(Context context) { public static void clearTokens(Context context) { SharedPreferences prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); - prefs.edit().remove(KEY_ACCESS_TOKEN).apply(); + prefs.edit() + .remove(KEY_ACCESS_TOKEN) + .remove(KEY_REFRESH_TOKEN) + .apply(); } } diff --git a/app/src/main/java/com/example/chain_g/common/RetrofitClient.java b/app/src/main/java/com/example/chain_g/common/RetrofitClient.java index c34d1af..9d7c79d 100644 --- a/app/src/main/java/com/example/chain_g/common/RetrofitClient.java +++ b/app/src/main/java/com/example/chain_g/common/RetrofitClient.java @@ -1,19 +1,84 @@ package com.example.chain_g.common; +import android.content.Context; +import com.example.chain_g.auth.dto.response.LoginResponse; +import com.example.chain_g.auth.jwt.TokenManager; +import okhttp3.Authenticator; +import okhttp3.Interceptor; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; import retrofit2.Retrofit; import retrofit2.converter.gson.GsonConverterFactory; public class RetrofitClient { private static final String BASE_URL = "http://10.0.2.2:8080/"; private static Retrofit retrofit = null; + private static ApiService apiService = null; - public static ApiService getApiService() { + public static ApiService getApiService(Context context) { if (retrofit == null) { + Context appContext = context.getApplicationContext(); + + Interceptor authInterceptor = chain -> { + String accessToken = TokenManager.getAccessToken(appContext); + Request.Builder builder = chain.request().newBuilder(); + if (accessToken != null) { + builder.addHeader("Authorization", "Bearer " + accessToken); + } + return chain.proceed(builder.build()); + }; + + Authenticator tokenAuthenticator = (route, response) -> { + if (responseCount(response) >= 2) { + return null; + } + + String refreshToken = TokenManager.getRefreshToken(appContext); + if (refreshToken == null) return null; + + ApiService reissueService = new Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(ApiService.class); + + retrofit2.Response> reissueResponse = + reissueService.reissue("Bearer " + refreshToken).execute(); + + if (reissueResponse.isSuccessful() && reissueResponse.body() != null) { + LoginResponse data = reissueResponse.body().getData(); + TokenManager.saveTokens(appContext, data.getAccessToken(), data.getRefreshToken()); + + return response.request().newBuilder() + .header("Authorization", "Bearer " + data.getAccessToken()) + .build(); + } + return null; + }; + + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(authInterceptor) + .authenticator(tokenAuthenticator) + .build(); + retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) + .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); + + apiService = retrofit.create(ApiService.class); + } + return apiService; + } + + // 재시도 횟수를 계산 + private static int responseCount(Response response) { + int result = 1; + while ((response = response.priorResponse()) != null) { + result++; } - return retrofit.create(ApiService.class); + return result; } } From adcd7abe82898934f07ab2bbdaf8313b7f830de8 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Fri, 6 Mar 2026 17:41:17 +0900 Subject: [PATCH 20/25] =?UTF-8?q?[refactor]=20#3=20=EB=B3=B8=EC=82=AC=20?= =?UTF-8?q?=EA=B3=84=EC=A0=95=EC=9D=80=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9E=AC=EC=84=A4=EC=A0=95=20=EB=B6=88=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/activity/FindAccountActivity.java | 64 ++++++++++++++++++- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/chain_g/auth/activity/FindAccountActivity.java b/app/src/main/java/com/example/chain_g/auth/activity/FindAccountActivity.java index cffab7a..e6cafa9 100644 --- a/app/src/main/java/com/example/chain_g/auth/activity/FindAccountActivity.java +++ b/app/src/main/java/com/example/chain_g/auth/activity/FindAccountActivity.java @@ -1,15 +1,25 @@ package com.example.chain_g.auth.activity; import android.os.Bundle; +import android.widget.EditText; import android.widget.TextView; +import android.widget.Toast; import androidx.activity.EdgeToEdge; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import com.example.chain_g.R; +import com.example.chain_g.auth.dto.request.ResetPasswordRequest; +import com.example.chain_g.common.ApiResponse; +import com.example.chain_g.common.RetrofitClient; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; public class FindAccountActivity extends AppCompatActivity { @@ -18,16 +28,64 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_find_account); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); - TextView BackButton = findViewById(R.id.back_btn); + // 1. 뷰 연결 + EditText idEditText = findViewById(R.id.editTextText2); // 아이디 입력창 + EditText emailEditText = findViewById(R.id.editTextText); // 이메일 입력창 + TextView sendButton = findViewById(R.id.button6); // 발송 버튼 + TextView backButton = findViewById(R.id.back_btn); // 뒤로가기 버튼 + + // 2. 발송 버튼 클릭 시 + sendButton.setOnClickListener(v -> { + String loginId = idEditText.getText().toString().trim(); + String email = emailEditText.getText().toString().trim(); + + // 유효성 검사 + if (loginId.isEmpty() || email.isEmpty()) { + Toast.makeText(this, "아이디와 이메일을 모두 입력해주세요.", Toast.LENGTH_SHORT).show(); + return; + } - BackButton.setOnClickListener(v -> { - finish(); + // [마초의 해결책] HQ(Headquarter) 본사 관리자 필터링 + // 소문자로 변환해서 체크하면 HQ, hq 둘 다 막을 수 있습니다. + if (loginId.toLowerCase().startsWith("hq")) { + Toast.makeText(this, "접근 권한이 없습니다.", Toast.LENGTH_LONG).show(); + return; // 서버에 요청도 안 보내고 여기서 끝! + } + + ResetPasswordRequest request = new ResetPasswordRequest(loginId, email); + + // API 호출 + RetrofitClient.getApiService(this).resetPassword(request).enqueue(new Callback>() { + @Override + public void onResponse(@NonNull Call> call, @NonNull Response> response) { + if (response.isSuccessful()) { + Toast.makeText(FindAccountActivity.this, "재설정 메일이 발송되었습니다.", Toast.LENGTH_LONG).show(); + finish(); + } else { + // 서버에서 만약 다른 이유로 403을 줘도 대응 가능하도록 유지 + if (response.code() == 403) { + Toast.makeText(FindAccountActivity.this, "접근 권한이 없습니다. 시스템 관리자에게 문의하세요.", Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(FindAccountActivity.this, "입력하신 정보가 일치하지 않습니다.", Toast.LENGTH_SHORT).show(); + } + } + } + + @Override + public void onFailure(@NonNull Call> call, @NonNull Throwable t) { + Toast.makeText(FindAccountActivity.this, "서버 연결 실패: " + t.getMessage(), Toast.LENGTH_SHORT).show(); + } + }); }); + + // 뒤로가기 버튼 + backButton.setOnClickListener(v -> finish()); } } \ No newline at end of file From 941f58c229bed2bb38d51b4b1fbe21f6ab1cb878 Mon Sep 17 00:00:00 2001 From: yoocy0 Date: Thu, 12 Mar 2026 19:39:38 +0900 Subject: [PATCH 21/25] =?UTF-8?q?[feat]=20#5=20=EC=95=88=EB=93=9C=EB=A1=9C?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=20=EC=8A=A4=EC=BA=94=20api=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/deploymentTargetSelector.xml | 2 +- app/src/main/AndroidManifest.xml | 4 + .../com/example/chain_g/BoxScanActivity.java | 197 +++++++++++- .../example/chain_g/FacInScanActivity.java | 285 +++++++++++++++++ .../chain_g/FacManagerMainActivity.java | 8 +- .../com/example/chain_g/InDetailActivity.java | 85 ++++- .../com/example/chain_g/ProductAdapter.java | 10 + .../com/example/chain_g/ScanActivity.java | 298 +++++++++++++++++- .../example/chain_g/common/ApiResponse.java | 5 +- .../example/chain_g/common/ApiService.java | 40 +++ .../chain_g/common/RetrofitClient.java | 26 +- .../dto/request/InboundScanBoxRequest.java | 21 ++ .../dto/request/InboundScanItemRequest.java | 40 +++ .../dto/request/OutboundAssignRequest.java | 32 ++ .../dto/request/OutboundUpdateRequest.java | 22 ++ .../dto/response/InboundDetailResponse.java | 49 +++ .../dto/response/OutboundItemResponse.java | 49 +++ .../chain_g/viewmodel/ScanViewModel.java | 31 ++ app/src/main/res/layout/activity_box_scan.xml | 5 +- .../main/res/layout/activity_fac_in_scan.xml | 141 +++++++++ .../main/res/layout/activity_in_detail.xml | 5 +- app/src/main/res/layout/activity_scan.xml | 5 +- 22 files changed, 1321 insertions(+), 39 deletions(-) create mode 100644 app/src/main/java/com/example/chain_g/FacInScanActivity.java create mode 100644 app/src/main/java/com/example/chain_g/dto/request/InboundScanBoxRequest.java create mode 100644 app/src/main/java/com/example/chain_g/dto/request/InboundScanItemRequest.java create mode 100644 app/src/main/java/com/example/chain_g/dto/request/OutboundAssignRequest.java create mode 100644 app/src/main/java/com/example/chain_g/dto/request/OutboundUpdateRequest.java create mode 100644 app/src/main/java/com/example/chain_g/dto/response/InboundDetailResponse.java create mode 100644 app/src/main/java/com/example/chain_g/dto/response/OutboundItemResponse.java create mode 100644 app/src/main/java/com/example/chain_g/viewmodel/ScanViewModel.java create mode 100644 app/src/main/res/layout/activity_fac_in_scan.xml diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index 135f9de..d077c87 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,7 +4,7 @@ - - + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_product_result.xml b/app/src/main/res/layout/item_product_result.xml index 45863e0..0e172ef 100644 --- a/app/src/main/res/layout/item_product_result.xml +++ b/app/src/main/res/layout/item_product_result.xml @@ -13,6 +13,7 @@ + Date: Wed, 18 Mar 2026 12:46:36 +0900 Subject: [PATCH 23/25] =?UTF-8?q?[docs]=20=EC=95=88=EB=93=9C=EB=A1=9C?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=20README=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 137d571..d586f04 100644 --- a/README.md +++ b/README.md @@ -1 +1,64 @@ -# android \ No newline at end of file +# 📱 CHAIN-G (Android) + +Gemini_Generated_Image_8j8ddp8j8ddp8j8d-Photoroom + +> CONNECT GOOD, VALUE CHAIN +> +**CHAIN-G** Android 애플리케이션은 스마트 공급망 관리 시스템의 현장 업무를 지원하는 모바일 도구입니다. +공장 및 가맹점 관리자가 스마트폰을 이용해 제품 바코드를 스캔함으로써 실시간으로 재고를 처리하고 판매 내역을 기록할 수 있는 **스캔 중심의 현장 지원 앱**입니다. + +
+ +## 🛠 기술 스택 + +| Category | Stack | +| :--- | :--- | +| **Language** | Java | +| **UI Components** | Google Material Design, XML | +| **Scanning** | Google ML Kit Barcode Scanning, CameraX | +| **Networking** | Retrofit2, GSON | +| **Development Tool** | Android Studio | + +
+ +## 📂 프로젝트 구조 + +```bash +android/ +├── app/ +│ ├── src/main/ +│ │ ├── java/com/example/chain_g/ +│ │ │ ├── auth/ # 로그인 및 인증 로직 +│ │ │ ├── common/ # 공통 유틸 및 상단 바 +│ │ │ ├── BoxScanActivity.java # 박스 스캔 처리 +│ │ │ ├── SaleScanActivity.java # 판매 스캔 처리 +│ │ │ ├── FacManagerMainActivity.java # 공장 관리자 메인 +│ │ │ └── FranManagerMainActivity.java # 가맹점 관리자 메인 +│ │ └── res/ # XML 레이아웃 및 리소스 +└── build.gradle # 프로젝트 수준 빌드 설정 +``` + +
+ +## 🚀 주요 기능 + +- **실시간 바코드/QR 스캔**: Google ML Kit와 CameraX를 연동하여 빠르고 정확하게 제품/박스 코드를 인식합니다. +- **재고 입출고 정보 확인**: 스캔한 박스의 정보를 기반으로 상세 입고/출고 품목을 즉시 확인하고 수량을 체크합니다. +- **판매 등록 관리**: 가맹점에서 상품 판매 시 바코드 스캔을 통해 판매 데이터를 본사 시스템과 즉시 동기화합니다. +- **역할 기반 인터페이스**: 로그인한 사용자의 권한(공장/가맹점)에 따라 다른 메인 기능 탭과 서비스를 제공합니다. + +
+ +## 🖥️ 주요 화면 +- **로그인 및 실시간 바코드/QR 스캔** +image + +
+ +## 👥 팀 협업 규칙 + +### ⭐ 개발 규칙 및 컨벤션 + +- **Activity 및 Layout**: 기능별로 명확히 분리하며, 재사용 가능한 커스텀 뷰는 `common` 패키지에서 관리합니다. +- **API 연동**: Retrofit 인터페이스를 통해 백엔드 API 명세와 일치하도록 데이터 모델을 동기화합니다. +- **주석 준수**: 복잡한 스캔 로직이나 카메라 프리뷰 관련 코드는 주석을 통해 의도를 명시합니다. From 856c1838e5ddcedd1e117a3b509b6f92ccb20b8c Mon Sep 17 00:00:00 2001 From: yoocy0 Date: Wed, 18 Mar 2026 15:18:00 +0900 Subject: [PATCH 24/25] =?UTF-8?q?[fix]=20#7=20=EC=8A=A4=EC=BA=94=20?= =?UTF-8?q?=ED=9B=84=20=EB=AA=A9=EB=A1=9D=20=EC=9E=90=EB=8F=99=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/example/chain_g/BoxAdapter.java | 12 +++- .../com/example/chain_g/BoxScanActivity.java | 43 +++++++------- .../com/example/chain_g/ScanActivity.java | 57 +++++++------------ .../chain_g/common/RetrofitClient.java | 2 +- .../dto/request/OutboundAssignRequest.java | 12 +++- .../dto/request/OutboundUpdateRequest.java | 36 +++++++++--- .../chain_g/viewmodel/ScanViewModel.java | 15 +++++ 7 files changed, 108 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/com/example/chain_g/BoxAdapter.java b/app/src/main/java/com/example/chain_g/BoxAdapter.java index 60b214e..0a9650d 100644 --- a/app/src/main/java/com/example/chain_g/BoxAdapter.java +++ b/app/src/main/java/com/example/chain_g/BoxAdapter.java @@ -14,7 +14,7 @@ public class BoxAdapter extends RecyclerView.Adapter { private OnItemClickListener listener; public interface OnItemClickListener { - void onItemClick(String boxCode); + void onItemClick(BoxItem item); } public BoxAdapter(List items, OnItemClickListener listener) { @@ -36,7 +36,7 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) { holder.tvProductCode.setText(item.productCode); holder.tvItemName.setText(item.itemName); - holder.itemView.setOnClickListener(v -> listener.onItemClick(item.boxCode)); + holder.itemView.setOnClickListener(v -> listener.onItemClick(item)); } @Override @@ -59,11 +59,19 @@ public static class BoxItem { String boxCode; String productCode; String itemName; + Long orderItemId; public BoxItem(String boxCode, String productCode, String itemName) { this.boxCode = boxCode; this.productCode = productCode; this.itemName = itemName; } + + public BoxItem(String boxCode, String productCode, String itemName, Long orderItemId) { + this.boxCode = boxCode; + this.productCode = productCode; + this.itemName = itemName; + this.orderItemId = orderItemId; + } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/chain_g/BoxScanActivity.java b/app/src/main/java/com/example/chain_g/BoxScanActivity.java index efbd5cb..bcc522a 100644 --- a/app/src/main/java/com/example/chain_g/BoxScanActivity.java +++ b/app/src/main/java/com/example/chain_g/BoxScanActivity.java @@ -118,7 +118,6 @@ protected void onCreate(Bundle savedInstanceState) { // 2. 리스트 설정 (항목 클릭 시 moveToDetail 호출) boxItems = new ArrayList<>(); - boxItems.add(new BoxAdapter.BoxItem("BOX-TEST-001", "PROD-001", "테스트 박스 상품")); adapter = new BoxAdapter(boxItems, this::moveToDetail); // 👈 여기서 클릭 시 moveToDetail 실행! rvBoxList.setLayoutManager(new LinearLayoutManager(this)); @@ -270,25 +269,31 @@ public void onFailure(Call> call, Throwable t) { } else { // 출고(OUT) 모드 등: BOX 코드와 ORDER 코드 추출 String boxCode = null; - String orderCode = null; + Long orderItemId = null; + String orderCode = "ORDER-PENDING"; // Default value for orderCode try { JsonObject jsonObject = new JsonParser().parse(qrData).getAsJsonObject(); if (jsonObject.has("boxCode")) { boxCode = jsonObject.get("boxCode").getAsString().trim().toUpperCase(); } - if (jsonObject.has("orderCode")) { - orderCode = jsonObject.get("orderCode").getAsString().trim().toUpperCase(); + if (jsonObject.has("orderItemId")) { + orderItemId = jsonObject.get("orderItemId").getAsLong(); } + // If orderCode is also expected in JSON, add parsing here + // if (jsonObject.has("orderCode")) { + // orderCode = jsonObject.get("orderCode").getAsString().trim().toUpperCase(); + // } } catch (Exception e) { - Log.d(TAG, "출고 QR 파싱 실패, raw 데이터 사용 시도"); + Log.d(TAG, "출고 QR 파싱 실패, raw 데이터 사용 시도: " + e.getMessage()); } if (boxCode == null || boxCode.isEmpty()) { boxCode = qrData.trim().toUpperCase(); } - if (orderCode == null || orderCode.isEmpty()) { - orderCode = "ORDER-PENDING"; - } + // orderCode already has a default value "ORDER-PENDING" + // If you want to allow qrData to be the orderCode if it's not a boxCode, + // you'd need more complex logic here. For now, boxCode takes precedence. + // 중복 체크 for (BoxAdapter.BoxItem item : boxItems) { @@ -298,11 +303,11 @@ public void onFailure(Call> call, Throwable t) { } } - viewModel.setBoxData(boxCode, orderCode); - boxItems.add(0, new BoxAdapter.BoxItem(boxCode, orderCode, "스캔된 박스")); + viewModel.setBoxData(boxCode, orderCode, orderItemId); + boxItems.add(0, new BoxAdapter.BoxItem(boxCode, orderCode, "스캔된 박스", orderItemId)); adapter.notifyItemInserted(0); rvBoxList.scrollToPosition(0); - Log.d(TAG, "박스 스캔 완료(OUT): " + boxCode + " | Order: " + orderCode); + Log.d(TAG, "박스 스캔 완료(OUT): " + boxCode + " | OrderItemId: " + orderItemId); isScanning = true; } @@ -337,17 +342,14 @@ private void showErrorAndResume(String message) { // 🚀 모드별 화면 이동 로직 (완전 보강) - public void moveToDetail(String boxCode) { + public void moveToDetail(BoxAdapter.BoxItem item) { Intent intent; - - // 클릭된 박스에 해당하는 orderCode 찾기 (데이터 일관성 위해 ViewModel 체크) - String orderCode = "ORDER-UNKNOWN"; - if (boxCode.equals(viewModel.getBoxCodeValue())) { - orderCode = viewModel.getOrderCodeValue(); - } + String boxCode = item.boxCode; + String orderCode = item.productCode; // orderCode as productCode in BoxItem for now + Long orderItemId = item.orderItemId; if ("OUT".equals(currentMode)) { - Log.d(TAG, "출고 모드 감지: ScanActivity로 이동합니다."); + Log.d(TAG, "출고 모드 감지: ScanActivity로 이동합니다. OrderItemId: " + orderItemId); intent = new Intent(BoxScanActivity.this, ScanActivity.class); } else { Log.d(TAG, "입고 모드 감지: InDetailActivity로 이동합니다."); @@ -358,6 +360,9 @@ public void moveToDetail(String boxCode) { JsonObject jsonExport = new JsonObject(); jsonExport.addProperty("boxCode", boxCode); jsonExport.addProperty("orderCode", orderCode); + if (orderItemId != null) { + jsonExport.addProperty("orderItemId", orderItemId); + } intent.putExtra("selected_box", jsonExport.toString()); intent.putExtra("mode", currentMode); diff --git a/app/src/main/java/com/example/chain_g/ScanActivity.java b/app/src/main/java/com/example/chain_g/ScanActivity.java index b339775..6fc8e81 100644 --- a/app/src/main/java/com/example/chain_g/ScanActivity.java +++ b/app/src/main/java/com/example/chain_g/ScanActivity.java @@ -74,6 +74,7 @@ public class ScanActivity extends AppCompatActivity { private ProductAdapter adapter; private String currentBoxCode; private String currentOrderCode; + private Long currentOrderItemId; @Override @@ -105,7 +106,9 @@ protected void onCreate(Bundle savedInstanceState) { try { JsonObject jsonObject = new JsonParser().parse(selectedBoxJson).getAsJsonObject(); currentBoxCode = jsonObject.get("boxCode").getAsString(); - currentOrderCode = jsonObject.get("orderCode").getAsString(); + if (jsonObject.has("orderItemId")) { + currentOrderItemId = jsonObject.get("orderItemId").getAsLong(); + } if (tvBoxCode != null) { tvBoxCode.setText(currentBoxCode); } @@ -170,7 +173,7 @@ protected void onCreate(Bundle savedInstanceState) { } - OutboundAssignRequest request = new OutboundAssignRequest(currentBoxCode, serialCodes); + OutboundAssignRequest request = new OutboundAssignRequest(currentBoxCode, currentOrderItemId, serialCodes); ApiService apiService = RetrofitClient.getApiService(this); apiService.assignBox(request).enqueue(new Callback>() { @Override @@ -317,43 +320,21 @@ private void scanBarcodes(ImageProxy imageProxy) { private void handleScannedQr(String qrData) { Log.d(TAG, "QR 처리 시작: " + qrData); - List extractedCodes = new ArrayList<>(); + OutboundUpdateRequest requestBody; try { - com.google.gson.JsonElement element = new com.google.gson.JsonParser().parse(qrData); - if (element.isJsonObject()) { - com.google.gson.JsonObject jsonObject = element.getAsJsonObject(); - - // serialCodes 키에 대응 (대소문자 구분 없이) - com.google.gson.JsonArray array = null; - for (String key : jsonObject.keySet()) { - if (key.equalsIgnoreCase("serialCodes")) { - array = jsonObject.getAsJsonArray(key); - break; - } - } - - if (array != null) { - for (int i = 0; i < array.size(); i++) { - extractedCodes.add(array.get(i).getAsString().trim().toUpperCase()); - } - } else if (jsonObject.has("serialCode")) { - extractedCodes.add(jsonObject.get("serialCode").getAsString().trim().toUpperCase()); - } + requestBody = new com.google.gson.Gson().fromJson(qrData, OutboundUpdateRequest.class); + if (requestBody == null || requestBody.getSerialCode() == null) { + showErrorAndResume("유효하지 않은 QR 데이터입니다. (시리얼 코드 누락)"); + return; } } catch (Exception e) { Log.e(TAG, "QR 파싱 에러: " + e.getMessage()); - } - - if (extractedCodes.isEmpty()) { - showErrorAndResume("QR 코드에서 유효한 시리얼 코드를 찾을 수 없습니다."); + showErrorAndResume("QR 코드 형식이 올바르지 않습니다."); return; } - // 여러 개의 코드가 올 수 있지만, 현재 로직상 리스트의 첫 번째 항목을 주 타겟으로 하거나 - // 리스트 전체를 API로 보냅니다. (사용자 요청에 따라 리스트 형태로 전송) - final String mainSerialCode = extractedCodes.get(0); - Log.d(TAG, "최종 추출 시리얼 코드 리스트: " + extractedCodes); - + final String mainSerialCode = requestBody.getSerialCode().trim().toUpperCase(); + Log.d(TAG, "추출된 시리얼 코드: " + mainSerialCode); ProductAdapter.ProductItem existingItem = null; for(ProductAdapter.ProductItem item : productItems) { @@ -365,7 +346,7 @@ private void handleScannedQr(String qrData) { if (existingItem != null && existingItem.isScanned) { Log.d(TAG, "이미 스캔 완료된 항목임: " + mainSerialCode); - isScanning = true; // 다시 스캔 재개 + isScanning = true; return; } @@ -376,8 +357,6 @@ private void handleScannedQr(String qrData) { showLoading(true); - OutboundUpdateRequest requestBody = new OutboundUpdateRequest(extractedCodes); - Log.d(TAG, "API 호출: /api/v1/outbounds/scans | Body: " + new com.google.gson.Gson().toJson(requestBody)); @@ -393,8 +372,12 @@ public void onResponse(Call> call, Response> finalItem.isScanned = true; adapter.notifyDataSetChanged(); } else { - // 목록에 없던 제품이면 새로 추가 - ProductAdapter.ProductItem newItem = new ProductAdapter.ProductItem(mainSerialCode, "PROD-X", "스캔 제품", "2024-05-20", "2025-05-20"); + // 목록에 없던 제품이면 새로 추가 (QR에서 받아온 정보 활용) + String prodDisplay = requestBody.getProductId() != null ? "ID: " + requestBody.getProductId() : "PROD-X"; + String dateDisplay = requestBody.getManufactureDate() != null ? requestBody.getManufactureDate() : "미정"; + + ProductAdapter.ProductItem newItem = new ProductAdapter.ProductItem( + mainSerialCode, prodDisplay, "스캔된 제품", dateDisplay, "-"); newItem.isScanned = true; productItems.add(0, newItem); adapter.notifyItemInserted(0); diff --git a/app/src/main/java/com/example/chain_g/common/RetrofitClient.java b/app/src/main/java/com/example/chain_g/common/RetrofitClient.java index 53cf1e5..9ae1520 100644 --- a/app/src/main/java/com/example/chain_g/common/RetrofitClient.java +++ b/app/src/main/java/com/example/chain_g/common/RetrofitClient.java @@ -14,7 +14,7 @@ import retrofit2.converter.gson.GsonConverterFactory; public class RetrofitClient { - private static final String BASE_URL = "http://10.127.237.28:8080/"; + private static final String BASE_URL = "http://192.168.0.30:8080/"; private static Retrofit retrofit = null; private static ApiService apiService = null; diff --git a/app/src/main/java/com/example/chain_g/dto/request/OutboundAssignRequest.java b/app/src/main/java/com/example/chain_g/dto/request/OutboundAssignRequest.java index b87267e..f2d9718 100644 --- a/app/src/main/java/com/example/chain_g/dto/request/OutboundAssignRequest.java +++ b/app/src/main/java/com/example/chain_g/dto/request/OutboundAssignRequest.java @@ -4,13 +4,15 @@ public class OutboundAssignRequest { private String boxCode; + private Long orderItemId; private List serialCodes; public OutboundAssignRequest() { } - public OutboundAssignRequest(String boxCode, List serialCodes) { + public OutboundAssignRequest(String boxCode, Long orderItemId, List serialCodes) { this.boxCode = boxCode; + this.orderItemId = orderItemId; this.serialCodes = serialCodes; } @@ -22,6 +24,14 @@ public void setBoxCode(String boxCode) { this.boxCode = boxCode; } + public Long getOrderItemId() { + return orderItemId; + } + + public void setOrderItemId(Long orderItemId) { + this.orderItemId = orderItemId; + } + public List getSerialCodes() { return serialCodes; } diff --git a/app/src/main/java/com/example/chain_g/dto/request/OutboundUpdateRequest.java b/app/src/main/java/com/example/chain_g/dto/request/OutboundUpdateRequest.java index 87b1c37..9498a78 100644 --- a/app/src/main/java/com/example/chain_g/dto/request/OutboundUpdateRequest.java +++ b/app/src/main/java/com/example/chain_g/dto/request/OutboundUpdateRequest.java @@ -1,22 +1,40 @@ package com.example.chain_g.dto.request; -import java.util.List; - public class OutboundUpdateRequest { - private List serialCodes; + private String serialCode; + private Long productId; + private String manufactureDate; public OutboundUpdateRequest() { } - public OutboundUpdateRequest(List serialCodes) { - this.serialCodes = serialCodes; + public OutboundUpdateRequest(String serialCode, Long productId, String manufactureDate) { + this.serialCode = serialCode; + this.productId = productId; + this.manufactureDate = manufactureDate; + } + + public String getSerialCode() { + return serialCode; + } + + public void setSerialCode(String serialCode) { + this.serialCode = serialCode; + } + + public Long getProductId() { + return productId; + } + + public void setProductId(Long productId) { + this.productId = productId; } - public List getSerialCodes() { - return serialCodes; + public String getManufactureDate() { + return manufactureDate; } - public void setSerialCodes(List serialCodes) { - this.serialCodes = serialCodes; + public void setManufactureDate(String manufactureDate) { + this.manufactureDate = manufactureDate; } } diff --git a/app/src/main/java/com/example/chain_g/viewmodel/ScanViewModel.java b/app/src/main/java/com/example/chain_g/viewmodel/ScanViewModel.java index 9dd0456..c268e4d 100644 --- a/app/src/main/java/com/example/chain_g/viewmodel/ScanViewModel.java +++ b/app/src/main/java/com/example/chain_g/viewmodel/ScanViewModel.java @@ -7,12 +7,19 @@ public class ScanViewModel extends ViewModel { private final MutableLiveData boxCode = new MutableLiveData<>(); private final MutableLiveData orderCode = new MutableLiveData<>(); + private final MutableLiveData orderItemId = new MutableLiveData<>(); public void setBoxData(String boxCode, String orderCode) { this.boxCode.setValue(boxCode); this.orderCode.setValue(orderCode); } + public void setBoxData(String boxCode, String orderCode, Long orderItemId) { + this.boxCode.setValue(boxCode); + this.orderCode.setValue(orderCode); + this.orderItemId.setValue(orderItemId); + } + public LiveData getBoxCode() { return boxCode; } @@ -21,6 +28,10 @@ public LiveData getOrderCode() { return orderCode; } + public LiveData getOrderItemId() { + return orderItemId; + } + public String getBoxCodeValue() { return boxCode.getValue(); } @@ -28,4 +39,8 @@ public String getBoxCodeValue() { public String getOrderCodeValue() { return orderCode.getValue(); } + + public Long getOrderItemIdValue() { + return orderItemId.getValue(); + } } From 698f12a9f8af73b1e033df1b997ed3ee029bf776 Mon Sep 17 00:00:00 2001 From: yoocy0 Date: Wed, 18 Mar 2026 20:44:05 +0900 Subject: [PATCH 25/25] =?UTF-8?q?[fix]=20#10=20=ED=8C=90=C3=AB=ED=8C=90?= =?UTF-8?q?=EB=A7=A4=20=EC=83=9D=EC=84=B1=20dto=20=C3=AA=EC=A0=84=EB=8B=AC?= =?UTF-8?q?=20=EA=B0=92=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/chain_g/SaleScanActivity.java | 31 ++++++++++++++++--- .../chain_g/common/RetrofitClient.java | 2 +- .../dto/request/FranchiseSellItemRequest.java | 5 ++- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/example/chain_g/SaleScanActivity.java b/app/src/main/java/com/example/chain_g/SaleScanActivity.java index ab710c1..5335ac1 100644 --- a/app/src/main/java/com/example/chain_g/SaleScanActivity.java +++ b/app/src/main/java/com/example/chain_g/SaleScanActivity.java @@ -189,11 +189,21 @@ private void scanBarcodes(ImageProxy imageProxy) { String rawValue = barcode.getRawValue(); if (rawValue != null) { String code = rawValue.trim(); + + // QR 데이터에서 시리얼 코드 미리 추출하여 중복 체크 강화 + String extractedCode = code.toUpperCase(); + try { + com.google.gson.JsonObject jsonObject = new com.google.gson.JsonParser().parse(code).getAsJsonObject(); + if (jsonObject.has("serialCode")) { + extractedCode = jsonObject.get("serialCode").getAsString().trim().toUpperCase(); + } + } catch (Exception ignored) {} + synchronized (this) { if (isScanning) { boolean alreadyInList = false; for (String existing : serialCodes) { - if (existing.equalsIgnoreCase(code)) { + if (existing.equalsIgnoreCase(extractedCode)) { alreadyInList = true; break; } @@ -201,7 +211,8 @@ private void scanBarcodes(ImageProxy imageProxy) { if (!alreadyInList) { isScanning = false; - runOnUiThread(() -> handleScannedQr(code)); + final String dataToPass = code; + runOnUiThread(() -> handleScannedQr(dataToPass)); break; } } @@ -213,14 +224,25 @@ private void scanBarcodes(ImageProxy imageProxy) { } private void handleScannedQr(String qrData) { + String extractedSerialCode = qrData; + try { + com.google.gson.JsonObject jsonObject = new com.google.gson.JsonParser().parse(qrData).getAsJsonObject(); + if (jsonObject.has("serialCode")) { + extractedSerialCode = jsonObject.get("serialCode").getAsString().trim().toUpperCase(); + } + } catch (Exception e) { + Log.d(TAG, "QR 데이터가 JSON 형식이 아니므로 일반 텍스트로 처리합니다."); + extractedSerialCode = qrData.trim().toUpperCase(); + } + for (String existing : serialCodes) { - if (existing.equalsIgnoreCase(qrData)) { + if (existing.equalsIgnoreCase(extractedSerialCode)) { isScanning = true; return; } } - serialCodes.add(qrData); + serialCodes.add(extractedSerialCode); fetchScannedSalesList(); } @@ -291,6 +313,7 @@ private void performSale() { item.getProductId(), item.getProductCode(), item.getProductName(), + 1, item.getUnitPrice(), item.getSerialCode() )); diff --git a/app/src/main/java/com/example/chain_g/common/RetrofitClient.java b/app/src/main/java/com/example/chain_g/common/RetrofitClient.java index 9ae1520..215d65a 100644 --- a/app/src/main/java/com/example/chain_g/common/RetrofitClient.java +++ b/app/src/main/java/com/example/chain_g/common/RetrofitClient.java @@ -14,7 +14,7 @@ import retrofit2.converter.gson.GsonConverterFactory; public class RetrofitClient { - private static final String BASE_URL = "http://192.168.0.30:8080/"; + private static final String BASE_URL = "http://192.168.90.43:8080/"; private static Retrofit retrofit = null; private static ApiService apiService = null; diff --git a/app/src/main/java/com/example/chain_g/dto/request/FranchiseSellItemRequest.java b/app/src/main/java/com/example/chain_g/dto/request/FranchiseSellItemRequest.java index dc75f1b..04fe1f1 100644 --- a/app/src/main/java/com/example/chain_g/dto/request/FranchiseSellItemRequest.java +++ b/app/src/main/java/com/example/chain_g/dto/request/FranchiseSellItemRequest.java @@ -6,13 +6,15 @@ public class FranchiseSellItemRequest { private Long productId; private String productCode; private String productName; + private Integer quantity; private BigDecimal unitPrice; private String serialCode; - public FranchiseSellItemRequest(Long productId, String productCode, String productName, BigDecimal unitPrice, String serialCode) { + public FranchiseSellItemRequest(Long productId, String productCode, String productName, Integer quantity, BigDecimal unitPrice, String serialCode) { this.productId = productId; this.productCode = productCode; this.productName = productName; + this.quantity = quantity; this.unitPrice = unitPrice; this.serialCode = serialCode; } @@ -20,6 +22,7 @@ public FranchiseSellItemRequest(Long productId, String productCode, String produ public Long getProductId() { return productId; } public String getProductCode() { return productCode; } public String getProductName() { return productName; } + public Integer getQuantity() { return quantity; } public BigDecimal getUnitPrice() { return unitPrice; } public String getSerialCode() { return serialCode; } }