KKL Workshop

January 27, 2024 (5mo ago)

KKL Workshop app

Overview:

A handy app that combines workshop and store management functionalities, simplifying tasks like assigning vehicle bays, coordinating mechanics, tracking work hours, and handling in-house orders. Everything you need, all in one easy-to-use platform. Additionally KKL Workshop app seamlessly integrates with local SAP and AWS cloud, ensuring live data access for smooth operations.

Impact:

  • Over 50% Efficiency Boost: Clients saw a significant increase in efficiency, streamlining operations by over 50%.
  • Enhanced Connectivity: Replacing RFID scanners with a mobile SAP client entirely resolved connectivity issues, ensuring smooth inventory management.
  • Enhanced Accessibility: Workers gained flexibility, able to work from anywhere without laptops, improving accessibility and mobility.

Journey:

When I joined the team, the app was already in a usable state, albeit with only vehicle and workshop management features. Previous developers had chosen to write it in Objective-C. Consequently, I decided to follow suit, prioritizing efficiency to avoid spending excessive time converting everything to Swift. We utilized AWS DynamoDB and S3 for storing operational data, with Lambda and EventBridge managing automations efficiently. Notably, a standout feature of this project was the integration with SAP, which I took the lead on.

At the client's request, we avoided sole dependency on the cloud database and instead pursued integration with their local SAP system. This collaboration involved working closely with the SAP vendor to integrate their existing SOAP-based web server API, a fitting choice given our project's Objective-C foundation. Given the ever-changing nature of business needs, instead of manually serializing requests using NSURLConnection, I developed a highly customizable SOAP serializer.

Below is an example class with a serializer:

@interface KKLItemMaster : SoapObject<NSCoding>
{
    NSString* _companyDB;
    NSString* _ItemCode;
    NSString* _ItemName;
    NSString* _InvntItem;
    NSString* _ManSerByn;
    NSString* _ItmsGrpName;
    int _ItmsGrpCod;
    double _OnHand;
    double _LastPurchasePrice;
    NSMutableArray* _ItemWarehouses;
    
}

@property (retain, nonatomic) NSString* companyDB;
@property (retain, nonatomic) NSString* ItemCode;
@property (retain, nonatomic) NSString* ItemName;
@property (retain, nonatomic) NSString* InvntItem;
@property (retain, nonatomic) NSString* ManSerByn;
@property (retain, nonatomic) NSString* ItmsGrpName;
@property int ItmsGrpCod;
@property double OnHand;
@property double LastPurchasePrice;
@property (retain, nonatomic) NSMutableArray* ItemWarehouses;

+ (KKLItemMaster*) createWithNode: (CXMLNode*) node;
- (id) initWithNode: (CXMLNode*) node;
- (NSMutableString*) serialize;
- (NSMutableString*) serialize: (NSString*) nodeName;
- (NSMutableString*) serializeAttributes;
- (NSMutableString*) serializeElements;

@end

@implementation KKLItemMaster
@synthesize companyDB = _companyDB;
@synthesize ItemCode = _ItemCode;
@synthesize ItemName = _ItemName;
@synthesize InvntItem = _InvntItem;
@synthesize ManSerByn = _ManSerByn;
@synthesize ItmsGrpName = _ItmsGrpName;
@synthesize ItmsGrpCod = _ItmsGrpCod;
@synthesize OnHand = _OnHand;
@synthesize LastPurchasePrice = _LastPurchasePrice;
@synthesize ItemWarehouses = _ItemWarehouses;

- (id) init
{
    if(self = [super init])
    {
        self.companyDB = nil;
        self.ItemCode = nil;
        self.ItemName = nil;
        self.InvntItem = nil;
        self.ManSerByn = nil;
        self.ItmsGrpName = nil;
        self.ItemWarehouses = [[NSMutableArray alloc] init];
        
    }
    return self;
}

- (id) initWithNode: (CXMLNode*) node {
    if(self = [super initWithNode: node])
    {
        self.companyDB = [Soap getNodeValue: node withName: @"companyDB"];
        self.ItemCode = [Soap getNodeValue: node withName: @"ItemCode"];
        self.ItemName = [Soap getNodeValue: node withName: @"ItemName"];
        self.InvntItem = [Soap getNodeValue: node withName: @"InvntItem"];
        self.ManSerByn = [Soap getNodeValue: node withName: @"ManSerByn"];
        self.ItmsGrpName = [Soap getNodeValue: node withName: @"ItmsGrpName"];
        self.ItmsGrpCod = [[Soap getNodeValue: node withName: @"ItmsGrpCod"] intValue];
        self.OnHand = [[Soap getNodeValue: node withName: @"OnHand"] doubleValue];
        self.LastPurchasePrice = [[Soap getNodeValue: node withName: @"LastPurchasePrice"] doubleValue];
        self.ItemWarehouses = [[KKLArrayOfItemWarehouses createWithNode: [Soap getNode: node withName: @"ItemWarehouses"]] object];
    }
    return self;
}

- (NSMutableString*) serialize
{
    return [self serialize: @"ItemMaster"];
}

- (NSMutableString*) serialize: (NSString*) nodeName
{
    NSMutableString* s = [NSMutableString string];
    [s appendFormat: @"<%@", nodeName];
    [s appendString: [self serializeAttributes]];
    [s appendString: @">"];
    [s appendString: [self serializeElements]];
    [s appendFormat: @"</%@>", nodeName];
    return s;
}

- (NSMutableString*) serializeElements
{
    NSMutableString* s = [super serializeElements];
    if (self.companyDB != nil) [s appendFormat: @"<companyDB>%@</companyDB>", [[self.companyDB stringByReplacingOccurrencesOfString:@"\"" withString:@"&quot;"] stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"]];
    if (self.ItemCode != nil) [s appendFormat: @"<ItemCode>%@</ItemCode>", [[self.ItemCode stringByReplacingOccurrencesOfString:@"\"" withString:@"&quot;"] stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"]];
    if (self.ItemName != nil) [s appendFormat: @"<ItemName>%@</ItemName>", [[self.ItemName stringByReplacingOccurrencesOfString:@"\"" withString:@"&quot;"] stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"]];
    if (self.InvntItem != nil) [s appendFormat: @"<InvntItem>%@</InvntItem>", [[self.InvntItem stringByReplacingOccurrencesOfString:@"\"" withString:@"&quot;"] stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"]];
    if (self.ManSerByn != nil) [s appendFormat: @"<ManSerByn>%@</ManSerByn>", [[self.ManSerByn stringByReplacingOccurrencesOfString:@"\"" withString:@"&quot;"] stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"]];
    if (self.ItmsGrpName != nil) [s appendFormat: @"<ItmsGrpName>%@</ItmsGrpName>", [[self.ItmsGrpName stringByReplacingOccurrencesOfString:@"\"" withString:@"&quot;"] stringByReplacingOccurrencesOfString:@"&" withString:@"&amp;"]];
    [s appendFormat: @"<ItmsGrpCod>%@</ItmsGrpCod>", [NSString stringWithFormat: @"%i", self.ItmsGrpCod]];
    [s appendFormat: @"<OnHand>%@</OnHand>", [NSString stringWithFormat: @"%f", self.OnHand]];
    [s appendFormat: @"<LastPurchasePrice>%@</LastPurchasePrice>", [NSString stringWithFormat: @"%f", self.LastPurchasePrice]];
    if (self.ItemWarehouses != nil && self.ItemWarehouses.count > 0) {
        [s appendFormat: @"<ItemWarehouses>%@</ItemWarehouses>", [KKLArrayOfItemWarehouses serialize: self.ItemWarehouses]];
    } else {
        [s appendString: @"<ItemWarehouses/>"];
    }
    
    return s;
}

- (NSMutableString*) serializeAttributes
{
    NSMutableString* s = [super serializeAttributes];
    
    return s;
}

@end

By comforming to my custom SOAPObject protocol, I managed to save hours of testing and retrofitting features. This approach enabled me to efficiently update both the class and serializer. Despite utilizing NSURLConnection, I chose to encapsulate it within a manager for improved reusability and easy maintenance when paired with the SOAPObject protocol.

Below is the example of the requester class:

	-(SoapRequest*)CreateItemWithProgress:(SoapRequestProgressBlock)progressBlock item: (KKLItemMaster*) item completion:(SoapRequestCompletionBlock)completionBlock {
		NSMutableArray* _params = [NSMutableArray array];
		
		[_params addObject: [[SoapParameter alloc] initWithValue: item forName: @"item"]];
		NSString* _envelope = [Soap createEnvelope:@"CreateItem" forNamespace:self.namespace withParameters:_params withHeaders:self.headers];
		SoapRequest* _request = [SoapRequest createWithService:self soapAction:@"sg.com.twm/CreateItem" postData:_envelope deserializeTo:[KKLResult alloc] completionBlock:completionBlock];
		_request.progressBlock = progressBlock;
		[_request send];
		return _request;
	}
💡

Technologies used to build : Swift, Objective-C, UIKit, Cacaopods, AWS, Firebase, SOAP