Skip to main content

Branch Management

Manajemen cabang dan multi-branch support

๐ŸŽฏ Overview

Branch adalah salah satu fitur utama dalam MStore Mobile yang menyediakan fungsionalitas untuk manajemen cabang dan multi-branch support.

๐Ÿ“‹ Features

  • โœ… Branch CRUD
  • โœ… Branch switching
  • โœ… Branch-specific inventory
  • โœ… Branch reports
  • โœ… Inter-branch transfer

๐Ÿ—๏ธ Architecture

BLoC Implementation

BLoC: BranchBloc
// Events
abstract class BranchEvent extends Equatable {}

class LoadBranch extends BranchEvent {}
class CreateBranch extends BranchEvent {}
class UpdateBranch extends BranchEvent {}
class DeleteBranch extends BranchEvent {}

// States
abstract class BranchState extends Equatable {}

class BranchInitial extends BranchState {}
class BranchLoading extends BranchState {}
class BranchLoaded extends BranchState {}
class BranchError extends BranchState {}

// BLoC
class BranchBloc extends Bloc<BranchEvent, BranchState> {
  final BranchRepository _repository;
  
  BranchBloc({required BranchRepository repository})
      : _repository = repository,
        super(BranchInitial()) {
    on<LoadBranch>(_onLoad);
    on<CreateBranch>(_onCreate);
    on<UpdateBranch>(_onUpdate);
    on<DeleteBranch>(_onDelete);
  }
  
  Future<void> _onLoad(
    LoadBranch event,
    Emitter<BranchState> emit,
  ) async {
    emit(BranchLoading());
    
    final result = await _repository.getBranchs();
    
    result.fold(
      (failure) => emit(BranchError(failure.message)),
      (data) => emit(BranchLoaded(data)),
    );
  }
}

Repository Pattern

abstract class BranchRepository {
  Future<Either<Failure, List<Branch>>> getBranchs();
  Future<Either<Failure, Branch>> getBranchById(String id);
  Future<Either<Failure, Branch>> createBranch(Branch data);
  Future<Either<Failure, Branch>> updateBranch(String id, Branch data);
  Future<Either<Failure, void>> deleteBranch(String id);
}

class BranchRepositoryImpl implements BranchRepository {
  final BranchApi _api;
  final BranchLocalRepository _localRepo;
  
  @override
  Future<Either<Failure, List<Branch>>> getBranchs() async {
    try {
      // Try local first (offline-first)
      final local = await _localRepo.getBranchs();
      
      // Sync with API in background
      final result = await _api.getBranchs();
      result.fold(
        (failure) => null,
        (data) => _localRepo.saveBranchs(data),
      );
      
      return Right(local.isNotEmpty ? local : result.getOrElse(() => []));
    } catch (e) {
      return Left(UnexpectedFailure(e.toString()));
    }
  }
}

๐Ÿ“ก API Integration

Endpoints

  • /api/v1/branches/*

Request/Response Examples

Get List

GET /api/v1/branches/*
Authorization: Bearer {access_token}
Response:
{
  "success": true,
  "data": [
    {
      "id": "123",
      "name": "Example",
      "created_at": "2024-10-14T10:00:00Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 100
  }
}

๐Ÿ’พ Local Database (Isar)

Schema

  • BranchLocal
@collection
class BranchLocal {
  Id id = Isar.autoIncrement;
  
  @Index()
  String? branchId;
  
  String? name;
  DateTime? createdAt;
  DateTime? updatedAt;
  DateTime? syncedAt;
  
  bool? isSynced;
  bool? isDeleted;
}

Queries

// Get all
final items = await isar.branchLocals.where().findAll();

// Get by ID
final item = await isar.branchLocals
    .filter()
    .branchIdEqualTo(id)
    .findFirst();

// Search
final results = await isar.branchLocals
    .filter()
    .nameContains(query, caseSensitive: false)
    .findAll();

// Get unsynced
final unsynced = await isar.branchLocals
    .filter()
    .isSyncedEqualTo(false)
    .findAll();

๐Ÿ”„ Offline-First Strategy

Write Operations

  1. Save to local Isar immediately
  2. Show success to user
  3. Add to sync queue
  4. Background sync when online
  5. Update with server response

Read Operations

  1. Read from local Isar (fast)
  2. Show to user immediately
  3. Background fetch from API
  4. Update local cache if changed
  5. Notify UI if data updated

Conflict Resolution

  • Strategy: Last-write-wins
  • Timestamp: Server timestamp as source of truth
  • Logging: All conflicts logged for audit

๐ŸŽจ UI Components

Main Screen

class BranchPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => getIt<BranchBloc>()..add(LoadBranch()),
      child: Scaffold(
        appBar: AppBar(title: Text('Branch Management')),
        body: BlocBuilder<BranchBloc, BranchState>(
          builder: (context, state) {
            if (state is BranchLoading) {
              return Center(child: CircularProgressIndicator());
            }
            
            if (state is BranchError) {
              return ErrorWidget(message: state.message);
            }
            
            if (state is BranchLoaded) {
              return BranchListView(items: state.items);
            }
            
            return SizedBox.shrink();
          },
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () => _navigateToCreate(context),
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

๐Ÿงช Testing

Unit Tests

void main() {
  group('BranchBloc', () {
    late BranchBloc bloc;
    late MockBranchRepository mockRepository;

    setUp(() {
      mockRepository = MockBranchRepository();
      bloc = BranchBloc(repository: mockRepository);
    });

    tearDown(() {
      bloc.close();
    });

    test('initial state is BranchInitial', () {
      expect(bloc.state, equals(BranchInitial()));
    });

    blocTest<BranchBloc, BranchState>(
      'emits [Loading, Loaded] when Load succeeds',
      build: () {
        when(() => mockRepository.getBranchs()).thenAnswer(
          (_) async => Right([Branch(id: '1', name: 'Test')]),
        );
        return bloc;
      },
      act: (bloc) => bloc.add(LoadBranch()),
      expect: () => [
        BranchLoading(),
        isA<BranchLoaded>(),
      ],
    );
  });
}

๐Ÿ“Š Performance Considerations

  • Lazy Loading: Load data on demand
  • Pagination: Implement pagination for large datasets
  • Caching: Cache frequently accessed data
  • Indexing: Use Isar indexes for fast queries
  • Background Sync: Sync in background to avoid blocking UI

๐Ÿ” Security

  • Authorization: Check user permissions before operations
  • Data Encryption: Sensitive data encrypted in Isar
  • Input Validation: Validate all user inputs
  • Audit Trail: Log all operations for audit

๐Ÿ“ฑ Platform-Specific

iOS

  • Use Cupertino widgets
  • Follow iOS HIG
  • Handle safe area insets

Android

  • Use Material widgets
  • Follow Material Design
  • Handle back button

Last Updated: October 14, 2024
Status: โœ… Production Ready