feat: 대규모 코드베이스 개선 - 백엔드 통합성 강화 및 UI 일관성 완성
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled

- CLAUDE.md 대폭 개선: 개발 가이드라인 및 프로젝트 상태 문서화
- 백엔드 API 통합: 모든 엔티티 간 Foreign Key 관계 완벽 구현
- UI 일관성 강화: shadcn_ui 컴포넌트 표준화 적용
- 데이터 모델 개선: DTO 및 모델 클래스 백엔드 스키마와 100% 일치
- 사용자 관리: 회사 연결, 중복 검사, 입력 검증 기능 추가
- 창고 관리: 우편번호 연결, 중복 검사 기능 강화
- 회사 관리: 우편번호 연결, 중복 검사 로직 구현
- 장비 관리: 불필요한 카테고리 필드 제거, 벤더-모델 관계 정리
- 우편번호 시스템: 검색 다이얼로그 Provider 버그 수정

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-08-31 15:49:05 +09:00
parent 9dec6f1034
commit df7dd8dacb
46 changed files with 2148 additions and 2722 deletions

View File

@@ -3,58 +3,46 @@ import 'package:freezed_annotation/freezed_annotation.dart';
part 'user_model.freezed.dart';
part 'user_model.g.dart';
/// 사용자 도메인 엔티티 (서버 API v0.2.1 스키마 대응)
/// 권한: admin(관리자), manager(매니저), staff(직원)
/// 사용자 도메인 엔티티 (백엔드 호환 + UI 필드)
/// 백엔드 users 테이블: id, name, phone, email, companies_id
@freezed
class User with _$User {
const factory User({
/// 사용자 ID (자동 생성)
int? id,
/// 사용자명 (로그인용, 필수, 유니크, 3자 이상)
required String username,
/// 이메일 (필수, 유니크)
required String email,
/// 이름 (필수)
required String name,
/// 이메일 (선택)
String? email,
/// 전화번호 (선택, "010-1234-5678" 형태)
String? phone,
/// 권한 (필수: admin, manager, staff)
required UserRole role,
/// 활성화 상태 (기본값: true)
@Default(true) bool isActive,
/// 생성일시 (자동 입력)
DateTime? createdAt,
/// 수정일시 (자동 갱신)
DateTime? updatedAt,
/// UI용 필드들 (백엔드 저장하지 않음)
@Default('') String username, // UI 호환용
@Default(UserRole.staff) UserRole role, // UI 호환용
@Default(true) bool isActive, // UI 호환용
DateTime? createdAt, // UI 호환용
DateTime? updatedAt, // UI 호환용
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
/// 사용자 권한 열거형 (서버 API 스키마 대응)
/// 사용자 권한 열거형 (UI 호환용)
@JsonEnum()
enum UserRole {
/// 관리자 - 전체 시스템 관리 권한
@JsonValue('admin')
admin,
/// 매니저 - 중간 관리 권한
@JsonValue('manager')
manager,
/// 직원 - 기본 사용 권한
@JsonValue('staff')
staff;
/// 권한 한글명 반환
String get displayName {
switch (this) {
case UserRole.admin:
@@ -66,18 +54,6 @@ enum UserRole {
}
}
/// 권한 레벨 반환 (높을수록 상위 권한)
int get level {
switch (this) {
case UserRole.admin:
return 3;
case UserRole.manager:
return 2;
case UserRole.staff:
return 1;
}
}
/// 문자열로부터 UserRole 생성
static UserRole fromString(String value) {
switch (value.toLowerCase()) {
@@ -88,40 +64,11 @@ enum UserRole {
case 'staff':
return UserRole.staff;
default:
throw ArgumentError('Unknown user role: $value');
return UserRole.staff;
}
}
}
/// 레거시 권한 시스템 호환성 유틸리티
/// 기존 S/M 코드와의 호환성을 위해 임시 유지
class LegacyUserRoles {
static const String admin = 'S'; // 관리자 (삭제 예정)
static const String member = 'M'; // 멤버 (삭제 예정)
/// 레거시 권한을 새 권한으로 변환
static UserRole toLegacyRole(String legacyRole) {
switch (legacyRole) {
case 'S':
return UserRole.admin;
case 'M':
return UserRole.staff;
default:
return UserRole.staff;
}
}
/// 새 권한을 레거시 권한으로 변환 (임시)
static String fromLegacyRole(UserRole role) {
switch (role) {
case UserRole.admin:
return 'S';
case UserRole.manager:
case UserRole.staff:
return 'M';
}
}
}
/// 전화번호 유틸리티
class PhoneNumberUtil {
@@ -160,8 +107,8 @@ class PhoneNumberUtil {
}
/// UI에서 서버용 전화번호 조합 ({prefix: "010", number: "12345678"} → "010-1234-5678")
static String combineFromUI(String prefix, String number) {
if (number.isEmpty) return '';
static String combineFromUI(String? prefix, String? number) {
if (prefix == null || prefix.isEmpty || number == null || number.isEmpty) return '';
final cleanNumber = number.replaceAll(RegExp(r'[^\d]'), '');
if (cleanNumber.length == 7) {

View File

@@ -23,28 +23,20 @@ mixin _$User {
/// 사용자 ID (자동 생성)
int? get id => throw _privateConstructorUsedError;
/// 사용자명 (로그인용, 필수, 유니크, 3자 이상)
String get username => throw _privateConstructorUsedError;
/// 이메일 (필수, 유니크)
String get email => throw _privateConstructorUsedError;
/// 이름 (필수)
String get name => throw _privateConstructorUsedError;
/// 이메일 (선택)
String? get email => throw _privateConstructorUsedError;
/// 전화번호 (선택, "010-1234-5678" 형태)
String? get phone => throw _privateConstructorUsedError;
/// 권한 (필수: admin, manager, staff)
UserRole get role => throw _privateConstructorUsedError;
/// 활성화 상태 (기본값: true)
bool get isActive => throw _privateConstructorUsedError;
/// 생성일시 (자동 입력)
DateTime? get createdAt => throw _privateConstructorUsedError;
/// 수정일시 (자동 갱신)
/// UI용 필드들 (백엔드 저장하지 않음)
String get username => throw _privateConstructorUsedError; // UI 호환용
UserRole get role => throw _privateConstructorUsedError; // UI 호환용
bool get isActive => throw _privateConstructorUsedError; // UI 호환용
DateTime? get createdAt => throw _privateConstructorUsedError; // UI 호환용
DateTime? get updatedAt => throw _privateConstructorUsedError;
/// Serializes this User to a JSON map.
@@ -63,10 +55,10 @@ abstract class $UserCopyWith<$Res> {
@useResult
$Res call(
{int? id,
String username,
String email,
String name,
String? email,
String? phone,
String username,
UserRole role,
bool isActive,
DateTime? createdAt,
@@ -89,10 +81,10 @@ class _$UserCopyWithImpl<$Res, $Val extends User>
@override
$Res call({
Object? id = freezed,
Object? username = null,
Object? email = null,
Object? name = null,
Object? email = freezed,
Object? phone = freezed,
Object? username = null,
Object? role = null,
Object? isActive = null,
Object? createdAt = freezed,
@@ -103,22 +95,22 @@ class _$UserCopyWithImpl<$Res, $Val extends User>
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int?,
username: null == username
? _value.username
: username // ignore: cast_nullable_to_non_nullable
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
email: freezed == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
phone: freezed == phone
? _value.phone
: phone // ignore: cast_nullable_to_non_nullable
as String?,
username: null == username
? _value.username
: username // ignore: cast_nullable_to_non_nullable
as String,
role: null == role
? _value.role
: role // ignore: cast_nullable_to_non_nullable
@@ -148,10 +140,10 @@ abstract class _$$UserImplCopyWith<$Res> implements $UserCopyWith<$Res> {
@useResult
$Res call(
{int? id,
String username,
String email,
String name,
String? email,
String? phone,
String username,
UserRole role,
bool isActive,
DateTime? createdAt,
@@ -171,10 +163,10 @@ class __$$UserImplCopyWithImpl<$Res>
@override
$Res call({
Object? id = freezed,
Object? username = null,
Object? email = null,
Object? name = null,
Object? email = freezed,
Object? phone = freezed,
Object? username = null,
Object? role = null,
Object? isActive = null,
Object? createdAt = freezed,
@@ -185,22 +177,22 @@ class __$$UserImplCopyWithImpl<$Res>
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int?,
username: null == username
? _value.username
: username // ignore: cast_nullable_to_non_nullable
as String,
email: null == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String,
name: null == name
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String,
email: freezed == email
? _value.email
: email // ignore: cast_nullable_to_non_nullable
as String?,
phone: freezed == phone
? _value.phone
: phone // ignore: cast_nullable_to_non_nullable
as String?,
username: null == username
? _value.username
: username // ignore: cast_nullable_to_non_nullable
as String,
role: null == role
? _value.role
: role // ignore: cast_nullable_to_non_nullable
@@ -226,11 +218,11 @@ class __$$UserImplCopyWithImpl<$Res>
class _$UserImpl implements _User {
const _$UserImpl(
{this.id,
required this.username,
required this.email,
required this.name,
this.email,
this.phone,
required this.role,
this.username = '',
this.role = UserRole.staff,
this.isActive = true,
this.createdAt,
this.updatedAt});
@@ -242,42 +234,40 @@ class _$UserImpl implements _User {
@override
final int? id;
/// 사용자명 (로그인용, 필수, 유니크, 3자 이상)
@override
final String username;
/// 이메일 (필수, 유니크)
@override
final String email;
/// 이름 (필수)
@override
final String name;
/// 이메일 (선택)
@override
final String? email;
/// 전화번호 (선택, "010-1234-5678" 형태)
@override
final String? phone;
/// 권한 (필수: admin, manager, staff)
/// UI용 필드들 (백엔드 저장하지 않음)
@override
@JsonKey()
final String username;
// UI 호환용
@override
@JsonKey()
final UserRole role;
/// 활성화 상태 (기본값: true)
// UI 호환용
@override
@JsonKey()
final bool isActive;
/// 생성일시 (자동 입력)
// UI 호환용
@override
final DateTime? createdAt;
/// 수정일시 (자동 갱신)
// UI 호환용
@override
final DateTime? updatedAt;
@override
String toString() {
return 'User(id: $id, username: $username, email: $email, name: $name, phone: $phone, role: $role, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt)';
return 'User(id: $id, name: $name, email: $email, phone: $phone, username: $username, role: $role, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt)';
}
@override
@@ -286,11 +276,11 @@ class _$UserImpl implements _User {
(other.runtimeType == runtimeType &&
other is _$UserImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.email, email) || other.email == email) &&
(identical(other.phone, phone) || other.phone == phone) &&
(identical(other.username, username) ||
other.username == username) &&
(identical(other.email, email) || other.email == email) &&
(identical(other.name, name) || other.name == name) &&
(identical(other.phone, phone) || other.phone == phone) &&
(identical(other.role, role) || other.role == role) &&
(identical(other.isActive, isActive) ||
other.isActive == isActive) &&
@@ -302,7 +292,7 @@ class _$UserImpl implements _User {
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, id, username, email, name, phone,
int get hashCode => Object.hash(runtimeType, id, name, email, phone, username,
role, isActive, createdAt, updatedAt);
/// Create a copy of User
@@ -324,11 +314,11 @@ class _$UserImpl implements _User {
abstract class _User implements User {
const factory _User(
{final int? id,
required final String username,
required final String email,
required final String name,
final String? email,
final String? phone,
required final UserRole role,
final String username,
final UserRole role,
final bool isActive,
final DateTime? createdAt,
final DateTime? updatedAt}) = _$UserImpl;
@@ -339,35 +329,27 @@ abstract class _User implements User {
@override
int? get id;
/// 사용자명 (로그인용, 필수, 유니크, 3자 이상)
@override
String get username;
/// 이메일 (필수, 유니크)
@override
String get email;
/// 이름 (필수)
@override
String get name;
/// 이메일 (선택)
@override
String? get email;
/// 전화번호 (선택, "010-1234-5678" 형태)
@override
String? get phone;
/// 권한 (필수: admin, manager, staff)
/// UI용 필드들 (백엔드 저장하지 않음)
@override
UserRole get role;
/// 활성화 상태 (기본값: true)
String get username; // UI 호환용
@override
bool get isActive;
/// 생성일시 (자동 입력)
UserRole get role; // UI 호환용
@override
DateTime? get createdAt;
/// 수정일시 (자동 갱신)
bool get isActive; // UI 호환용
@override
DateTime? get createdAt; // UI 호환용
@override
DateTime? get updatedAt;

View File

@@ -8,11 +8,12 @@ part of 'user_model.dart';
_$UserImpl _$$UserImplFromJson(Map<String, dynamic> json) => _$UserImpl(
id: (json['id'] as num?)?.toInt(),
username: json['username'] as String,
email: json['email'] as String,
name: json['name'] as String,
email: json['email'] as String?,
phone: json['phone'] as String?,
role: $enumDecode(_$UserRoleEnumMap, json['role']),
username: json['username'] as String? ?? '',
role: $enumDecodeNullable(_$UserRoleEnumMap, json['role']) ??
UserRole.staff,
isActive: json['isActive'] as bool? ?? true,
createdAt: json['createdAt'] == null
? null
@@ -25,10 +26,10 @@ _$UserImpl _$$UserImplFromJson(Map<String, dynamic> json) => _$UserImpl(
Map<String, dynamic> _$$UserImplToJson(_$UserImpl instance) =>
<String, dynamic>{
'id': instance.id,
'username': instance.username,
'email': instance.email,
'name': instance.name,
'email': instance.email,
'phone': instance.phone,
'username': instance.username,
'role': _$UserRoleEnumMap[instance.role]!,
'isActive': instance.isActive,
'createdAt': instance.createdAt?.toIso8601String(),

View File

@@ -8,6 +8,9 @@ class WarehouseLocation {
/// 주소 (단일 문자열)
final String? address;
/// 우편번호 (zipcodes_zipcode 필드)
final String? zipcode;
/// 담당자명
final String? managerName;
@@ -31,6 +34,7 @@ class WarehouseLocation {
required this.id,
required this.name,
this.address,
this.zipcode,
this.managerName,
this.managerPhone,
this.capacity,
@@ -44,6 +48,7 @@ class WarehouseLocation {
int? id,
String? name,
String? address,
String? zipcode,
String? managerName,
String? managerPhone,
int? capacity,
@@ -55,6 +60,7 @@ class WarehouseLocation {
id: id ?? this.id,
name: name ?? this.name,
address: address ?? this.address,
zipcode: zipcode ?? this.zipcode,
managerName: managerName ?? this.managerName,
managerPhone: managerPhone ?? this.managerPhone,
capacity: capacity ?? this.capacity,