diff --git a/AmiKoDesitin/Base.lproj/PatientViewController.xib b/AmiKoDesitin/Base.lproj/PatientViewController.xib index 99f3578..3fdf8b7 100644 --- a/AmiKoDesitin/Base.lproj/PatientViewController.xib +++ b/AmiKoDesitin/Base.lproj/PatientViewController.xib @@ -1,9 +1,11 @@ - + - + + + @@ -15,7 +17,9 @@ + + @@ -32,7 +36,7 @@ - + @@ -43,79 +47,91 @@ - + - + + + - + - - + + @@ -138,8 +154,8 @@ - - + + @@ -150,8 +166,8 @@ - - + + @@ -162,8 +178,8 @@ - - + + @@ -174,8 +190,8 @@ - - + + @@ -186,8 +202,8 @@ - - + + @@ -198,8 +214,8 @@ - - + + @@ -207,15 +223,15 @@ - + - - + + @@ -226,8 +242,8 @@ - - + + @@ -238,8 +254,8 @@ - - + + @@ -250,8 +266,8 @@ - - + + @@ -261,6 +277,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -269,6 +309,7 @@ + @@ -277,10 +318,10 @@ - - + + @@ -288,8 +329,15 @@ - + + + + + + + + diff --git a/AmiKoDesitin/DoctorViewController.m b/AmiKoDesitin/DoctorViewController.m index e9fdcad..d9359b7 100644 --- a/AmiKoDesitin/DoctorViewController.m +++ b/AmiKoDesitin/DoctorViewController.m @@ -312,8 +312,9 @@ - (BOOL) validateFields:(Operator *)doctor valid = FALSE; } + BOOL hinLoggedIn = [[MLPersistenceManager shared] HINSDSTokens] != nil || [[MLPersistenceManager shared] HINADSwissTokens] != nil; // Validate photo - if (!self.signatureView.image) { + if (!self.signatureView.image && !hinLoggedIn) { self.signatureView.backgroundColor = lightRed; valid = FALSE; } diff --git a/AmiKoDesitin/EPrescription/EPrescription.h b/AmiKoDesitin/EPrescription/EPrescription.h index f6620f8..e61e4a9 100644 --- a/AmiKoDesitin/EPrescription/EPrescription.h +++ b/AmiKoDesitin/EPrescription/EPrescription.h @@ -84,6 +84,7 @@ NS_ASSUME_NONNULL_BEGIN - (ZurRosePrescription *)toZurRosePrescription; - (NSDictionary *)amkDict; ++ (NSString *)swissKantonFromZip:(NSString *)zip; @end diff --git a/AmiKoDesitin/EPrescription/EPrescription.m b/AmiKoDesitin/EPrescription/EPrescription.m index 6239321..a7964a4 100644 --- a/AmiKoDesitin/EPrescription/EPrescription.m +++ b/AmiKoDesitin/EPrescription/EPrescription.m @@ -9,6 +9,7 @@ #import "EPrescription.h" #import "NSData+GZIP.h" #import "MLUtility.h" +#import "Patient.h" @implementation EPrescriptionPatientId @end @@ -214,7 +215,7 @@ - (ZurRosePrescription *)toZurRosePrescription { patient.firstName = self.patientFirstName; patient.street = self.patientStreet; patient.city = self.patientCity; - patient.kanton = [self swissKantonFromZip:self.patientZip]; + patient.kanton = [EPrescription swissKantonFromZip:self.patientZip]; patient.zipCode = self.patientZip; patient.birthday = self.patientBirthdate; patient.sex = [self.patientGender intValue]; // same, 1 = m, 2 = f @@ -275,7 +276,7 @@ - (ZurRosePrescription *)toZurRosePrescription { return prescription; } -- (NSString *)swissKantonFromZip:(NSString *)zip { ++ (NSString *)swissKantonFromZip:(NSString *)zip { if (!zip.length) return nil; NSURL *url = [[NSBundle mainBundle] URLForResource:@"swiss-zip-to-kanton" withExtension:@"json"]; NSData *data = [NSData dataWithContentsOfURL:url]; @@ -315,6 +316,8 @@ - (NSDictionary *)amkDict { }]; } + EPrescriptionPatientId *firstPatientId = self.patientIds.firstObject; + NSDictionary *amkDict = @{ @"prescription_hash": [[NSUUID UUID] UUIDString], // Normally place_date is composed with doctor's name or city, @@ -329,13 +332,13 @@ - (NSDictionary *)amkDict { @"given_name": self.patientLastName ?: @"", @"family_name": self.patientFirstName ?: @"", @"birth_date": self.patientBirthdate ? [birthDateDateFormatter stringFromDate:self.patientBirthdate] : @"", - @"gender": self.patientGender.intValue == 1 ? @"M" : @"F", + @"gender": self.patientGender.intValue == 1 ? KEY_AMK_PAT_GENDER_M : KEY_AMK_PAT_GENDER_F, @"email_address": self.patientEmail ?: @"", @"phone_number": self.patientPhone ?: @"", @"postal_address": self.patientStreet ?: @"", @"city": self.patientCity ?: @"", @"zip_code": self.patientZip ?: @"", - @"insurance_gln": self.patientReceiverGLN ?: @"", + @"insurance_gln": [firstPatientId.type isEqual:@1] ? firstPatientId.value : (self.patientReceiverGLN ?: @""), }, @"medications": mediDicts, }; diff --git a/AmiKoDesitin/EPrescription/ZurRosePrescription.m b/AmiKoDesitin/EPrescription/ZurRosePrescription.m index 5f1437a..d9e2759 100644 --- a/AmiKoDesitin/EPrescription/ZurRosePrescription.m +++ b/AmiKoDesitin/EPrescription/ZurRosePrescription.m @@ -25,7 +25,7 @@ - (DDXMLDocument *)toXML { NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.dateFormat = @"yyyy-MM-dd"; [e addAttribute:[DDXMLNode attributeWithName:@"issueDate" stringValue:[formatter stringFromDate:self.issueDate]]]; - [e addAttribute:[DDXMLNode attributeWithName:@"validity" stringValue:[formatter stringFromDate:self.validity]]]; + [e addAttribute:[DDXMLNode attributeWithName:@"validity" stringValue:self.validity ? [formatter stringFromDate:self.validity] : @""]]; [e addAttribute:[DDXMLNode attributeWithName:@"user" stringValue:self.user]]; [e addAttribute:[DDXMLNode attributeWithName:@"password" stringValue:self.password]]; if (self.prescriptionNr) { @@ -58,7 +58,9 @@ - (void)sendToZurRoseWithCompletion:(void (^)(NSHTTPURLResponse* res, NSError* e NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://estudio.zur-rose.ch/estudio/prescriptioncert"]]; [request setHTTPMethod:@"POST"]; [request setValue:@"application/xml" forHTTPHeaderField:@"Content-Type"]; - [request setHTTPBody:[[self toXML] XMLData]]; + NSData *xmlData = [[self toXML] XMLData]; + [request setHTTPBody:xmlData]; + NSLog(@"xml: %@", [[NSString alloc] initWithData:xmlData encoding:NSUTF8StringEncoding]); NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (![response isKindOfClass:[NSHTTPURLResponse class]]) return; callback((NSHTTPURLResponse*)response, error); diff --git a/AmiKoDesitin/EPrescription/ZurRoseProduct.h b/AmiKoDesitin/EPrescription/ZurRoseProduct.h index 6c3d272..80ff690 100644 --- a/AmiKoDesitin/EPrescription/ZurRoseProduct.h +++ b/AmiKoDesitin/EPrescription/ZurRoseProduct.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, strong) NSString *pharmacode; // optional @property (nonatomic, strong) NSString *eanId; // optional -@property (nonatomic, strong) NSString *description; // optional +@property (nonatomic, strong) NSString *description_; // optional @property (nonatomic, assign) BOOL repetition; @property (nonatomic, assign) int nrOfRepetitions; // optional, 0 - 99 @property (nonatomic, assign) int quantity; // 0 - 999 diff --git a/AmiKoDesitin/EPrescription/ZurRoseProduct.m b/AmiKoDesitin/EPrescription/ZurRoseProduct.m index cf5cd76..b6f0d01 100644 --- a/AmiKoDesitin/EPrescription/ZurRoseProduct.m +++ b/AmiKoDesitin/EPrescription/ZurRoseProduct.m @@ -34,8 +34,8 @@ - (DDXMLElement *)toXML { if (self.eanId) { [e addAttribute:[DDXMLNode attributeWithName:@"eanId" stringValue:self.eanId]]; } - if (self.description) { - [e addAttribute:[DDXMLNode attributeWithName:@"description" stringValue:self.description]]; + if (self.description_) { + [e addAttribute:[DDXMLNode attributeWithName:@"description" stringValue:self.description_]]; } [e addAttribute:[DDXMLNode attributeWithName:@"repetition" stringValue:self.repetition ? @"true" : @"false"]]; if (self.nrOfRepetitions >= 0) { diff --git a/AmiKoDesitin/MLSendToZurRoseActivity.h b/AmiKoDesitin/MLSendToZurRoseActivity.h new file mode 100644 index 0000000..00c9e18 --- /dev/null +++ b/AmiKoDesitin/MLSendToZurRoseActivity.h @@ -0,0 +1,20 @@ +// +// MLSendToZurRoseActivity.h +// AmikoDesitin +// +// Created by b123400 on 2024/12/23. +// Copyright © 2024 Ywesee GmbH. All rights reserved. +// + +#import +#import "EPrescription/ZurRosePrescription.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface MLSendToZurRoseActivity : UIActivity + +@property (nonatomic, strong) ZurRosePrescription *zurRosePrescription; + +@end + +NS_ASSUME_NONNULL_END diff --git a/AmiKoDesitin/MLSendToZurRoseActivity.m b/AmiKoDesitin/MLSendToZurRoseActivity.m new file mode 100644 index 0000000..949f30a --- /dev/null +++ b/AmiKoDesitin/MLSendToZurRoseActivity.m @@ -0,0 +1,58 @@ +// +// MLSendToZurRoseActivity.m +// AmikoDesitin +// +// Created by b123400 on 2024/12/23. +// Copyright © 2024 Ywesee GmbH. All rights reserved. +// + +#import "MLSendToZurRoseActivity.h" + +@implementation MLSendToZurRoseActivity + +- (NSString *)activityTitle { + return NSLocalizedString(@"Send to ZurRose", @""); +} + +- (UIImage *)activityImage { + return [UIImage systemImageNamed:@"paperplane"]; +} + +- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems { + return YES; +} + +- (void)prepareWithActivityItems:(NSArray *)activityItems { + +} + +- (void)performActivity { + [self.zurRosePrescription sendToZurRoseWithCompletion:^(NSHTTPURLResponse * _Nonnull res, NSError * _Nonnull error) { + dispatch_async(dispatch_get_main_queue(), ^{ + UIViewController *root = [[UIApplication sharedApplication].keyWindow rootViewController]; + if (error || res.statusCode != 200) { + UIAlertController *alert = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Error", @"") + message:[error localizedDescription] ?: [NSString stringWithFormat:NSLocalizedString(@"Error Code: %ld", @""), res.statusCode] + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"") + style:UIAlertActionStyleDefault + handler:nil]]; + [root presentViewController:alert animated:YES completion:nil]; + } else { + UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil + message:NSLocalizedString(@"Prescription is sent to ZurRose", @"") + preferredStyle:UIAlertControllerStyleAlert]; + [alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", @"") + style:UIAlertActionStyleDefault + handler:nil]]; + [root presentViewController:alert animated:YES completion:nil]; + } + }); + }]; +} + +- (UIActivityCategory)activityCategory { + return UIActivityCategoryShare; +} + +@end diff --git a/AmiKoDesitin/Model.xcdatamodeld/Model.xcdatamodel/contents b/AmiKoDesitin/Model.xcdatamodeld/Model.xcdatamodel/contents index 4141723..e80aa09 100644 --- a/AmiKoDesitin/Model.xcdatamodeld/Model.xcdatamodel/contents +++ b/AmiKoDesitin/Model.xcdatamodeld/Model.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -8,7 +8,9 @@ + + @@ -16,7 +18,4 @@ - - - \ No newline at end of file diff --git a/AmiKoDesitin/Patient.h b/AmiKoDesitin/Patient.h index ac55637..64ef688 100644 --- a/AmiKoDesitin/Patient.h +++ b/AmiKoDesitin/Patient.h @@ -23,6 +23,8 @@ #define KEY_AMK_PAT_COUNTRY @"country" #define KEY_AMK_PAT_PHONE @"phone_number" #define KEY_AMK_PAT_EMAIL @"email_address" +#define KEY_AMK_PAT_HEALTH_CARD_NUMBER @"health_card_number" +#define KEY_AMK_PAT_INSURANCE_GLN @"insurance_gln" @interface Patient : NSObject @@ -39,6 +41,8 @@ @property (atomic, copy) NSString *postalAddress; @property (atomic, copy) NSString *phoneNumber; @property (atomic, copy) NSString *emailAddress; +@property (atomic, copy) NSString *healthCardNumber; +@property (atomic, copy) NSString *insuranceGLN; // Only available when patient is read from database @property (nonatomic, strong, nullable) NSDate *timestamp; diff --git a/AmiKoDesitin/Patient.m b/AmiKoDesitin/Patient.m index c65875d..3d7f9c9 100644 --- a/AmiKoDesitin/Patient.m +++ b/AmiKoDesitin/Patient.m @@ -24,6 +24,8 @@ @implementation Patient @synthesize postalAddress; @synthesize phoneNumber; @synthesize emailAddress; +@synthesize healthCardNumber; +@synthesize insuranceGLN; - (void)importFromDict:(NSDictionary *)dict { @@ -40,6 +42,8 @@ - (void)importFromDict:(NSDictionary *)dict country = [self getString:KEY_AMK_PAT_COUNTRY orNilFromDict:dict]; phoneNumber = [self getString:KEY_AMK_PAT_PHONE orNilFromDict:dict]; emailAddress = [self getString:KEY_AMK_PAT_EMAIL orNilFromDict:dict]; + healthCardNumber = [self getString:KEY_AMK_PAT_HEALTH_CARD_NUMBER orNilFromDict:dict]; + insuranceGLN = [self getString:KEY_AMK_PAT_INSURANCE_GLN orNilFromDict:dict]; } - (NSDictionary *)dictionaryRepresentation { @@ -57,6 +61,8 @@ - (void)importFromDict:(NSDictionary *)dict [patientDict setObject:self.country ?: @"" forKey:KEY_AMK_PAT_COUNTRY]; [patientDict setObject:self.phoneNumber ?: @"" forKey:KEY_AMK_PAT_PHONE]; [patientDict setObject:self.emailAddress ?: @"" forKey:KEY_AMK_PAT_EMAIL]; + [patientDict setObject:self.healthCardNumber ?: @"" forKey:KEY_AMK_PAT_HEALTH_CARD_NUMBER]; + [patientDict setObject:self.insuranceGLN forKey:KEY_AMK_PAT_INSURANCE_GLN]; return patientDict; } diff --git a/AmiKoDesitin/PatientViewController+smartCard.h b/AmiKoDesitin/PatientViewController+smartCard.h index 9c8323a..eddf1d6 100644 --- a/AmiKoDesitin/PatientViewController+smartCard.h +++ b/AmiKoDesitin/PatientViewController+smartCard.h @@ -8,7 +8,7 @@ #import "PatientViewController.h" -#define NUMBER_OF_BOXES_FOR_OCR 3 +#define NUMBER_OF_BOXES_FOR_OCR 4 #import "CameraViewController.h" @@ -18,6 +18,7 @@ struct scannedResults { NSString *cardNumberString; NSString *dateString; NSString *sexString; + NSString *bagNumber; }; #pragma mark - class extension diff --git a/AmiKoDesitin/PatientViewController+smartCard.m b/AmiKoDesitin/PatientViewController+smartCard.m index 83ff49c..fd9c4c6 100644 --- a/AmiKoDesitin/PatientViewController+smartCard.m +++ b/AmiKoDesitin/PatientViewController+smartCard.m @@ -243,6 +243,25 @@ - (NSArray *)visionDetectTextBoundingBoxes:(CIImage*)image continue; } #endif + +#endif // DISCARD_BAD_BOXES + + NSArray *topCandidates = [obs topCandidates:1]; + NSString *s; + VNConfidence c = 0; + CGRect boundBox = CGRectZero; // text bounding box + if (topCandidates.count == 0) { + continue; + } + + // Use only first candidate + + s = topCandidates[0].string; + c = topCandidates[0].confidence; + id bbox = [topCandidates[0] boundingBoxForRange:NSMakeRange(0, s.length) error:nil]; + boundBox = [bbox boundingBox]; // observed that it's >= obs.boundingBox + //VNRecognizedText *t = topCandidates[0]; + // Discard text in the right area of the card // Keep only boxes with corner within 14mm strip on the left #ifdef CROP_IMAGE_TO_CARD_ROI @@ -257,32 +276,22 @@ - (NSArray *)visionDetectTextBoundingBoxes:(CIImage*)image if (box.origin.x > thresholdX) #endif { - continue; + // Handle BAG number, which is not at the right side of the card + // 5 digit and all number + if (s.length == 5 && [[NSString stringWithFormat:@"%05d",[s intValue]] isEqual:s]) { + if (box.origin.x > 0.9) { + continue; + } + } else { + continue; + } } // Discard text smaller than expected if (box.size.width < rejectBoxWidthFraction) { continue; } - -#endif // DISCARD_BAD_BOXES - - NSArray *topCandidates = [obs topCandidates:1]; - NSString *s; - VNConfidence c = 0; - CGRect boundBox = CGRectZero; // text bounding box - if (topCandidates.count == 0) { - continue; - } - - // Use only first candidate - - s = topCandidates[0].string; - c = topCandidates[0].confidence; - id bbox = [topCandidates[0] boundingBoxForRange:NSMakeRange(0, s.length) error:nil]; - boundBox = [bbox boundingBox]; // observed that it's >= obs.boundingBox - //VNRecognizedText *t = topCandidates[0]; - + #ifdef DISCARD_BAD_BOXES VNConfidence confidenceThreshold = 0.4f; if (c < confidenceThreshold) { @@ -324,6 +333,22 @@ - (NSArray *)visionDetectTextBoundingBoxes:(CIImage*)image - (NSArray *)analyzeVisionBoxedWords:(NSArray *)allBoxes { + // Always put BAG number at last + NSInteger bagNumberIndex = -1; + for (NSInteger i = 0; i < allBoxes.count; i++) { + if ([allBoxes[i][@"text"] length] == 5) { + bagNumberIndex = i; + break; + } + } + if (bagNumberIndex >= 0) { + NSMutableArray *mAllBoxes = [allBoxes mutableCopy]; + NSDictionary *bagNumber = mAllBoxes[bagNumberIndex]; + [mAllBoxes removeObjectAtIndex:bagNumberIndex]; + [mAllBoxes addObject:bagNumber]; + allBoxes = mAllBoxes; + } + NSUInteger n = [allBoxes count]; if (n < NUMBER_OF_BOXES_FOR_OCR) { return allBoxes; @@ -372,6 +397,12 @@ - (NSArray *)analyzeVisionBoxedWords:(NSArray *)allBoxes return p1.origin.x >= p2.origin.x; #else + if ([obj1[@"text"] length] == 5) { + return NSOrderedDescending; + } + if ([obj2[@"text"] length] == 5) { + return NSOrderedAscending; + } if (p1.origin.y == p2.origin.y) return NSOrderedSame; @@ -455,12 +486,15 @@ - (BOOL)validateOcrResults:(NSArray *)ocrResults { return NO; } + + NSString *bagNumber = ocrResults[3][@"text"]; savedOcr.familyName = familyName; savedOcr.givenName = givenName; savedOcr.cardNumberString = cardNumberString; savedOcr.dateString = dateString; savedOcr.sexString = sexString; + savedOcr.bagNumber = bagNumber; return YES; } @@ -477,6 +511,8 @@ - (void)lastVideoFrame:(NSNotification *)notification incompletePatient.givenName = savedOcr.givenName; incompletePatient.birthDate = savedOcr.dateString; incompletePatient.uniqueId = [incompletePatient generateUniqueID]; + incompletePatient.healthCardNumber = savedOcr.cardNumberString; + incompletePatient.insuranceGLN = [self bagNumberToInsuranceGLN:savedOcr.bagNumber]; if ([savedOcr.sexString isEqualToString:@"M"]) incompletePatient.gender = KEY_AMK_PAT_GENDER_M; @@ -520,4 +556,12 @@ - (void)lastVideoFrame:(NSNotification *)notification #endif } } + +- (NSString *)bagNumberToInsuranceGLN:(NSString *)bagNumber { + NSString *path = [[NSBundle mainBundle] pathForResource:@"bag-to-insurance-gln" ofType:@"json"]; + NSData *data = [[NSData alloc] initWithContentsOfFile:path]; + NSDictionary *mapping = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil]; + NSString *key = [NSString stringWithFormat:@"%d", bagNumber.intValue]; + return mapping[key]; +} @end diff --git a/AmiKoDesitin/PatientViewController.h b/AmiKoDesitin/PatientViewController.h index 66a5fc5..b71d1ab 100644 --- a/AmiKoDesitin/PatientViewController.h +++ b/AmiKoDesitin/PatientViewController.h @@ -30,6 +30,8 @@ IBOutlet UITextField *mCountry; IBOutlet UITextField *mPhone; IBOutlet UITextField *mEmail; + IBOutlet UITextField *mHealthCardNumber; + IBOutlet UITextField *mInsuranceGLN; IBOutlet UISegmentedControl *mSex; } diff --git a/AmiKoDesitin/PatientViewController.m b/AmiKoDesitin/PatientViewController.m index 04ff610..6fd9fd3 100644 --- a/AmiKoDesitin/PatientViewController.m +++ b/AmiKoDesitin/PatientViewController.m @@ -213,7 +213,9 @@ - (void) resetFieldsColors mCity.backgroundColor = mZipCode.backgroundColor = mSex.backgroundColor = - mEmail.backgroundColor = [UIColor secondarySystemBackgroundColor]; + mEmail.backgroundColor = + mHealthCardNumber.backgroundColor = + mInsuranceGLN.backgroundColor = [UIColor secondarySystemBackgroundColor]; } - (UIColor *)getInvalidFieldColor @@ -262,6 +264,8 @@ - (void) resetAllFields [mCountry setText:@""]; [mPhone setText:@""]; [mEmail setText:@""]; + [mHealthCardNumber setText:@""]; + [mInsuranceGLN setText:@""]; [mSex setSelectedSegmentIndex:UISegmentedControlNoSegment]; mPatientUUID = nil; @@ -306,6 +310,14 @@ - (void) setAllFields:(Patient *)p if (p.phoneNumber) [mPhone setText:p.phoneNumber]; + + if (p.healthCardNumber) { + [mHealthCardNumber setText:p.healthCardNumber]; + } + + if (p.insuranceGLN) { + [mInsuranceGLN setText:p.insuranceGLN]; + } if (p.uniqueId) mPatientUUID = p.uniqueId; @@ -349,6 +361,8 @@ - (Patient *) getAllFields patient.country = [mCountry text]; patient.phoneNumber = [mPhone text]; patient.emailAddress = [mEmail text]; + patient.healthCardNumber = [mHealthCardNumber text]; + patient.insuranceGLN = [mInsuranceGLN text]; switch ([mSex selectedSegmentIndex]) { case UISegmentedControlNoSegment: @@ -462,18 +476,27 @@ - (BOOL)textFieldShouldEndEditing:(UITextField *)textField (textField.tag == 11) || // phone (textField.tag == 12)) // email { - return TRUE; // Allow non mandatory fields to be empty + return YES; // Allow non mandatory fields to be empty } BOOL valid = TRUE; - if ([textField.text isEqualToString:@""]) - valid = FALSE; + + if (textField == mHealthCardNumber) { + NSUInteger length = mHealthCardNumber.text.length; + valid = length == 0 || length == 20; + } else if (textField == mInsuranceGLN) { + NSUInteger length = mInsuranceGLN.text.length; + valid = length == 0 || length == 13; + } else if ([textField.text isEqualToString:@""]) { + valid = NO; + } - if (valid) + if (valid) { textField.backgroundColor = [UIColor secondarySystemBackgroundColor]; - else + } else { textField.backgroundColor = [self getInvalidFieldColor]; - + } + return valid; } diff --git a/AmiKoDesitin/Persistence/MLPersistenceManager.m b/AmiKoDesitin/Persistence/MLPersistenceManager.m index 9821945..7c8e801 100644 --- a/AmiKoDesitin/Persistence/MLPersistenceManager.m +++ b/AmiKoDesitin/Persistence/MLPersistenceManager.m @@ -502,7 +502,9 @@ - (NSString *)upsertPatient:(Patient *)patient withTimestamp:(NSDate*)date updat p.phoneNumber = patient.phoneNumber; p.emailAddress = patient.emailAddress; p.gender = patient.gender; + p.healthCardNumber = patient.healthCardNumber; p.timestamp = date; + p.insuranceGLN = patient.insuranceGLN; [[self.coreDataContainer viewContext] save:&error]; if (error != nil) { NSLog(@"Cannot update patient %@", error); diff --git a/AmiKoDesitin/Prescription.h b/AmiKoDesitin/Prescription.h index d10e957..7886d35 100644 --- a/AmiKoDesitin/Prescription.h +++ b/AmiKoDesitin/Prescription.h @@ -10,6 +10,7 @@ #import "Patient.h" #import "Operator.h" #import "Product.h" +#import "ZurRosePrescription.h" #define KEY_AMK_HASH @"prescription_hash" #define KEY_AMK_PLACE_DATE @"place_date" @@ -32,5 +33,6 @@ - (NSArray *) makeMedicationsArray; - (NSData *)ePrescription; +- (ZurRosePrescription *)toZurRosePrescription; @end diff --git a/AmiKoDesitin/Prescription.m b/AmiKoDesitin/Prescription.m index e6aaf35..cba7f05 100644 --- a/AmiKoDesitin/Prescription.m +++ b/AmiKoDesitin/Prescription.m @@ -8,6 +8,8 @@ #import "Prescription.h" #import "LFCGzipUtility.h" +#import "EPrescription/EPrescription.h" +#import "MLConstants.h" @implementation Prescription @@ -179,6 +181,83 @@ - (NSData *)ePrescription { return prefixed; } +- (ZurRosePrescription *)toZurRosePrescription { + ZurRosePrescription *prescription = [[ZurRosePrescription alloc] init]; + + NSRange placeDateCommaRange = [self.placeDate rangeOfString:@","]; + if (placeDateCommaRange.location != NSNotFound) { + NSString *dateString = [[self.placeDate substringFromIndex:NSMaxRange(placeDateCommaRange)] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + NSDateFormatter *dateFormatter = [NSDateFormatter new]; + dateFormatter.dateFormat = @"dd.MM.yyyy (HH:mm:ss)"; + [dateFormatter setTimeZone:[NSTimeZone systemTimeZone]]; + NSDate *date = [dateFormatter dateFromString:self.placeDate]; + prescription.issueDate = date; + } else { + prescription.issueDate = [NSDate date]; + } + + prescription.prescriptionNr = [NSString stringWithFormat:@"%09d", arc4random_uniform(1000000000)]; + prescription.remark = @""; + prescription.validity = nil; + + prescription.user = @""; + prescription.password = @""; + prescription.deliveryType = ZurRosePrescriptionDeliveryTypePatient; + prescription.ignoreInteractions = NO; + prescription.interactionsWithOldPres = NO; + + ZurRosePrescriptorAddress *prescriptor = [[ZurRosePrescriptorAddress alloc] init]; + prescription.prescriptorAddress = prescriptor; + prescriptor.zsrId = self.doctor.zsrNumber; + prescriptor.firstName = self.doctor.givenName; + prescriptor.lastName = self.doctor.familyName; + + prescriptor.kanton = [EPrescription swissKantonFromZip:self.doctor.zipCode]; + prescriptor.email = self.doctor.emailAddress; + prescriptor.phoneNrBusiness = self.doctor.phoneNumber; + prescriptor.langCode = [[MLConstants databaseLanguage] isEqual:@"de"] ? 1 : 2; + prescriptor.clientNrClustertec = @"888870"; + prescriptor.street = self.doctor.postalAddress; + prescriptor.zipCode = self.doctor.zipCode; + prescriptor.city = self.doctor.city; + + ZurRosePatientAddress *patient = [[ZurRosePatientAddress alloc] init]; + prescription.patientAddress = patient; + patient.lastName = self.patient.familyName; + patient.firstName = self.patient.givenName; + patient.street = self.patient.postalAddress; + patient.city = self.patient.city; + patient.kanton = [EPrescription swissKantonFromZip:self.patient.zipCode]; + patient.zipCode = self.patient.zipCode; + { + NSDateFormatter *birthDateDateFormatter = [[NSDateFormatter alloc] init]; + birthDateDateFormatter.dateFormat = @"yyyy.MM.dd"; + patient.birthday = [birthDateDateFormatter dateFromString:self.patient.birthDate]; + } + patient.sex = [self.patient.gender isEqual:KEY_AMK_PAT_GENDER_M] ? 1 : 2; // same, 1 = m, 2 = f + patient.phoneNrHome = self.patient.phoneNumber; + patient.email = self.patient.emailAddress; + patient.langCode = [[MLConstants databaseLanguage] isEqual:@"de"] ? 1 : 2; + patient.coverCardId = self.patient.healthCardNumber ?: @""; + patient.patientNr = self.patient.insuranceGLN; + + NSMutableArray *products = [NSMutableArray array]; + for (Product *m in self.medications) { + ZurRoseProduct *product = [[ZurRoseProduct alloc] init]; + [products addObject:product]; + + product.eanId = m.eanCode; + product.quantity = 1; + product.insuranceBillingType = 1; + product.repetition = NO; + product.posology = @[]; + } + prescription.products = products; + + return prescription; +} + + - (NSString *)formatBirthdayForEPrescription:(NSString *)birthday { // dd.mm.yyyy -> yyyy-mm-dd NSArray *parts = [birthday componentsSeparatedByString:@"."]; diff --git a/AmiKoDesitin/PrescriptionViewController.m b/AmiKoDesitin/PrescriptionViewController.m index 6e8b93b..fd703f4 100644 --- a/AmiKoDesitin/PrescriptionViewController.m +++ b/AmiKoDesitin/PrescriptionViewController.m @@ -21,6 +21,7 @@ #import #import "MLHINADSwissAuthHandle.h" #import "EPrescription.h" +#import "MLSendToZurRoseActivity.h" @import Vision; @@ -1612,6 +1613,7 @@ - (void) showCameraForHealthCardOCR [patientVC handleCameraButton:nil]; } + - (void) showCameraForBarcodeAcquisition { // Make sure front is PrescriptionViewController @@ -2340,10 +2342,13 @@ - (void)sharePrescription:(NSURL *)urlAttachment withPdfData:(NSData *)pdfData { PrintItemProvider *source3 = [[PrintItemProvider alloc] initWithPlaceholderItem:pdfData]; NSArray *objectsToShare = @[mailBody, urlAttachment, source3]; + + MLSendToZurRoseActivity *zurRoseActivity = [[MLSendToZurRoseActivity alloc] init]; + zurRoseActivity.zurRosePrescription = self.prescription.toZurRosePrescription; UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:objectsToShare - applicationActivities:nil]; + applicationActivities:@[zurRoseActivity]]; NSArray *excludeActivities = @[UIActivityTypeCopyToPasteboard, UIActivityTypeAssignToContact, @@ -2589,7 +2594,7 @@ - (void)didScanEPrescription:(EPrescription *)ePrescription { NSLog(@"saved to: %@", savedURL); dispatch_async(dispatch_get_main_queue(), ^{ - UIAlertController *controller = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Send to ZurRose", @"") + UIAlertController *controller = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"Send to ZurRose?", @"") message:nil preferredStyle:UIAlertControllerStyleAlert]; [controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Send", @"") diff --git a/AmiKoDesitin/bag-to-insurance-gln.json b/AmiKoDesitin/bag-to-insurance-gln.json new file mode 100644 index 0000000..0f3ff27 --- /dev/null +++ b/AmiKoDesitin/bag-to-insurance-gln.json @@ -0,0 +1,45 @@ +{"1560" : "7601003000436", +"1507" : "7601003000504", +"32" : "7601003000184", +"1542" : "7601003001303", +"312" : "7601003000290", +"343" : "7601003004137", +"1402" : "7601003002270", +"1322" : "7601003000863", +"290" : "7601003001099", +"8" : "7601003001082", +"774" : "7601003006995", +"881" : "7601003000924", +"134" : "7601003000566", +"1386" : "7601003000054", +"1491" : "7601003000313", +"780" : "7601003000610", +"1562" : "7601003002775", +"1520" : "7601003001204", +"829" : "7601003002218", +"376" : "7601003000382", +"820" : "7601003001839", +"360" : "7601003002140", +"1522" : "7601003002300", +"1479" : "7601003004106", +"1179" : "7601003001235", +"455" : "7601003000894", +"1535" : "7601003019377", +"1401" : "7601003000788", +"1568" : "7601003010220", +"901" : "7601003001822", +"1509" : "7601003002294", +"923" : "7601003002263", +"941" : "7601003001891", +"246" : "7601003001686", +"194" : "7601003001693", +"62" : "7601003000016", +"1384" : "7601003002041", +"1113" : "7601003001273", +"1555" : "7601003000023", +"1040" : "7601003001907", +"966" : "7601003000795", +"1570" : "7601003011098", +"509" : "7601003000207", +"1318" : "7601003001921" +} diff --git a/AmiKoDesitin/de.lproj/Localizable.strings b/AmiKoDesitin/de.lproj/Localizable.strings index 570ae5d..14b31fd 100644 --- a/AmiKoDesitin/de.lproj/Localizable.strings +++ b/AmiKoDesitin/de.lproj/Localizable.strings @@ -299,7 +299,9 @@ /* No comment provided by engineer. */ "Your medicine basket is empty" = "Ihr Medikamentenkorb ist leer."; -"Send to ZurRose" = "Senden an ZurRose?"; +"Send to ZurRose" = "Senden an ZurRose"; + +"Send to ZurRose?" = "Senden an ZurRose?"; "Send" = "Senden"; diff --git a/AmiKoDesitin/fr.lproj/Localizable.strings b/AmiKoDesitin/fr.lproj/Localizable.strings index 58caf81..80eb8a7 100644 --- a/AmiKoDesitin/fr.lproj/Localizable.strings +++ b/AmiKoDesitin/fr.lproj/Localizable.strings @@ -293,7 +293,9 @@ /* No comment provided by engineer. */ "Your medicine basket is empty" = "Votre panier des médicaments est vide."; -"Send to ZurRose" = "Envoyer à ZurRose?"; +"Send to ZurRose" = "Envoyer à ZurRose"; + +"Send to ZurRose?" = "Envoyer à ZurRose?"; "Send" = "Envoyer"; diff --git a/AmikoDesitin.xcodeproj/project.pbxproj b/AmikoDesitin.xcodeproj/project.pbxproj index 5709b9e..95a1dc5 100644 --- a/AmikoDesitin.xcodeproj/project.pbxproj +++ b/AmikoDesitin.xcodeproj/project.pbxproj @@ -52,10 +52,14 @@ CF3684C02427BFE700066502 /* MLSettingViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CF3684BD2427BFE700066502 /* MLSettingViewController.m */; }; CF3684C12427BFE700066502 /* MLSettingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CF3684BE2427BFE700066502 /* MLSettingViewController.xib */; }; CF3684C22427BFE700066502 /* MLSettingViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = CF3684BE2427BFE700066502 /* MLSettingViewController.xib */; }; + CF3BBEDA2D19428B00EDC3F7 /* MLSendToZurRoseActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = CF3BBED92D19428B00EDC3F7 /* MLSendToZurRoseActivity.m */; }; + CF3BBEDB2D19428B00EDC3F7 /* MLSendToZurRoseActivity.m in Sources */ = {isa = PBXBuildFile; fileRef = CF3BBED92D19428B00EDC3F7 /* MLSendToZurRoseActivity.m */; }; CF40D7AB2426062100FDCFCD /* MLiCloudToLocalMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = CF40D7AA2426062100FDCFCD /* MLiCloudToLocalMigration.m */; }; CF40D7AC2426089900FDCFCD /* MLiCloudToLocalMigration.m in Sources */ = {isa = PBXBuildFile; fileRef = CF40D7AA2426062100FDCFCD /* MLiCloudToLocalMigration.m */; }; CF426A6524362DF200A107AB /* MLUbiquitousStateAlertController.m in Sources */ = {isa = PBXBuildFile; fileRef = CF426A6424362DF200A107AB /* MLUbiquitousStateAlertController.m */; }; CF426A6624362DF200A107AB /* MLUbiquitousStateAlertController.m in Sources */ = {isa = PBXBuildFile; fileRef = CF426A6424362DF200A107AB /* MLUbiquitousStateAlertController.m */; }; + CF44671A2D1C217A00549546 /* bag-to-insurance-gln.json in Resources */ = {isa = PBXBuildFile; fileRef = CF4467192D1C217A00549546 /* bag-to-insurance-gln.json */; }; + CF44671B2D1C217A00549546 /* bag-to-insurance-gln.json in Resources */ = {isa = PBXBuildFile; fileRef = CF4467192D1C217A00549546 /* bag-to-insurance-gln.json */; }; CF626256241BAD3C00FBCCD9 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF626255241BAD3C00FBCCD9 /* CloudKit.framework */; }; CF626257241BAD4400FBCCD9 /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF626255241BAD3C00FBCCD9 /* CloudKit.framework */; }; CF62625C241D0E6600FBCCD9 /* MLPersistenceManager.m in Sources */ = {isa = PBXBuildFile; fileRef = CF62625B241D0E6600FBCCD9 /* MLPersistenceManager.m */; }; @@ -373,10 +377,13 @@ CF3684BC2427BFE700066502 /* MLSettingViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MLSettingViewController.h; path = AmiKoDesitin/MLSettingViewController.h; sourceTree = SOURCE_ROOT; }; CF3684BD2427BFE700066502 /* MLSettingViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MLSettingViewController.m; path = AmiKoDesitin/MLSettingViewController.m; sourceTree = SOURCE_ROOT; }; CF3684BE2427BFE700066502 /* MLSettingViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MLSettingViewController.xib; path = AmiKoDesitin/MLSettingViewController.xib; sourceTree = SOURCE_ROOT; }; + CF3BBED82D19428B00EDC3F7 /* MLSendToZurRoseActivity.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MLSendToZurRoseActivity.h; path = AmiKoDesitin/MLSendToZurRoseActivity.h; sourceTree = SOURCE_ROOT; }; + CF3BBED92D19428B00EDC3F7 /* MLSendToZurRoseActivity.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MLSendToZurRoseActivity.m; path = AmiKoDesitin/MLSendToZurRoseActivity.m; sourceTree = SOURCE_ROOT; }; CF40D7A92426062100FDCFCD /* MLiCloudToLocalMigration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MLiCloudToLocalMigration.h; path = "AmiKoDesitin/Persistence/iCloud to local migration/MLiCloudToLocalMigration.h"; sourceTree = SOURCE_ROOT; }; CF40D7AA2426062100FDCFCD /* MLiCloudToLocalMigration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MLiCloudToLocalMigration.m; path = "AmiKoDesitin/Persistence/iCloud to local migration/MLiCloudToLocalMigration.m"; sourceTree = SOURCE_ROOT; }; CF426A6324362DF200A107AB /* MLUbiquitousStateAlertController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MLUbiquitousStateAlertController.h; path = AmiKoDesitin/Persistence/MLUbiquitousStateAlertController.h; sourceTree = SOURCE_ROOT; }; CF426A6424362DF200A107AB /* MLUbiquitousStateAlertController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = MLUbiquitousStateAlertController.m; path = AmiKoDesitin/Persistence/MLUbiquitousStateAlertController.m; sourceTree = SOURCE_ROOT; }; + CF4467192D1C217A00549546 /* bag-to-insurance-gln.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "bag-to-insurance-gln.json"; sourceTree = ""; }; CF626254241B7A9E00FBCCD9 /* CoMedDesitin.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CoMedDesitin.entitlements; sourceTree = ""; }; CF626255241BAD3C00FBCCD9 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; CF62625A241D0E6600FBCCD9 /* MLPersistenceManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MLPersistenceManager.h; path = AmiKoDesitin/Persistence/MLPersistenceManager.h; sourceTree = SOURCE_ROOT; }; @@ -778,6 +785,8 @@ 430285BC20160FB1003B2EF3 /* PrescriptionViewController.h */, 430285BD20160FB1003B2EF3 /* PrescriptionViewController.m */, 4387FC3F20189DB4000A5A0F /* PrescriptionViewController.xib */, + CF3BBED82D19428B00EDC3F7 /* MLSendToZurRoseActivity.h */, + CF3BBED92D19428B00EDC3F7 /* MLSendToZurRoseActivity.m */, ); name = front; sourceTree = ""; @@ -806,6 +815,7 @@ 439F261F2028C8A600396C29 /* PatientViewController.h */, 439F26202028C8A700396C29 /* PatientViewController.m */, FA4511BA204ECBCB00899A77 /* PatientViewController.xib */, + CF4467192D1C217A00549546 /* bag-to-insurance-gln.json */, ); name = front; sourceTree = ""; @@ -1431,6 +1441,7 @@ D5A22539194A3A400078FEFF /* 218-trash2.png in Resources */, D50BC99A1BDCFDBC008A2150 /* MLLaunchScreen.storyboard in Resources */, D533DAAC17B4351700199C38 /* Default@2x.png in Resources */, + CF44671B2D1C217A00549546 /* bag-to-insurance-gln.json in Resources */, D551D53F17F22A58000EC52A /* desitin_icon_120x120.png in Resources */, D554574C18B3E85D003299E0 /* 939-download-rectangle@2x.png in Resources */, D533DAAE17B435E300199C38 /* desitin_icon_72x72.png in Resources */, @@ -1518,6 +1529,7 @@ D5F6BB8117E8C87500DCBD16 /* desitin_icon_72x72.png in Resources */, FA4827EE22DF8618005135BE /* amiko_frequency_fr.db in Resources */, D5A2253A194A3A400078FEFF /* 218-trash2.png in Resources */, + CF44671A2D1C217A00549546 /* bag-to-insurance-gln.json in Resources */, D5F6BB8217E8C87500DCBD16 /* desitin_icon_144x144.png in Resources */, D5F6BB8E17E8C87500DCBD16 /* amiko_report_de.html in Resources */, D5F6BB8F17E8C87500DCBD16 /* amiko_report_fr.html in Resources */, @@ -1603,6 +1615,7 @@ 439F261D2028B7FF00396C29 /* LegacyPatientDBAdapter.m in Sources */, FA66294B204D541B0000904A /* DoctorViewController.m in Sources */, CFB672922A7FA7D5004A0063 /* LFCGzipUtility.m in Sources */, + CF3BBEDA2D19428B00EDC3F7 /* MLSendToZurRoseActivity.m in Sources */, CF7118E92D12C0AF00C1FA82 /* ZurRoseAddress.m in Sources */, CF7118EA2D12C0AF00C1FA82 /* EPrescription.m in Sources */, CF7118EB2D12C0AF00C1FA82 /* ZurRosePatientAddress.m in Sources */, @@ -1677,6 +1690,7 @@ 930D94B52077E5D9007E4629 /* LegacyPatientDBAdapter.m in Sources */, CFA5207B24235FD900834E16 /* PatientModel+CoreDataProperties.m in Sources */, CFB672942A7FA7E3004A0063 /* LFCGzipUtility.m in Sources */, + CF3BBEDB2D19428B00EDC3F7 /* MLSendToZurRoseActivity.m in Sources */, CF7118F22D12C0AF00C1FA82 /* ZurRoseAddress.m in Sources */, CF7118F32D12C0AF00C1FA82 /* EPrescription.m in Sources */, CF7118F42D12C0AF00C1FA82 /* ZurRosePatientAddress.m in Sources */, diff --git a/PatientModel+CoreDataClass.m b/PatientModel+CoreDataClass.m index 8ec287a..b091026 100644 --- a/PatientModel+CoreDataClass.m +++ b/PatientModel+CoreDataClass.m @@ -26,6 +26,8 @@ - (void)importFromPatient:(Patient *)p timestamp:(NSDate *)timestamp { self.postalAddress = p.postalAddress; self.phoneNumber = p.phoneNumber; self.emailAddress = p.emailAddress; + self.healthCardNumber = p.healthCardNumber; + self.insuranceGLN = p.insuranceGLN; } - (Patient *)toPatient { @@ -44,6 +46,8 @@ - (Patient *)toPatient { p.phoneNumber = self.phoneNumber; p.emailAddress = self.emailAddress; p.timestamp = self.timestamp; + p.healthCardNumber = self.healthCardNumber; + p.insuranceGLN = self.insuranceGLN; return p; } diff --git a/PatientModel+CoreDataProperties.h b/PatientModel+CoreDataProperties.h index 3132e51..ecd2ebe 100644 --- a/PatientModel+CoreDataProperties.h +++ b/PatientModel+CoreDataProperties.h @@ -29,7 +29,8 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable, nonatomic, copy) NSString *uniqueId; @property (nonatomic) int64_t weightKg; @property (nullable, nonatomic, copy) NSString *zipCode; - +@property (nullable, nonatomic, copy) NSString *healthCardNumber; +@property (nullable, nonatomic, copy) NSString *insuranceGLN; @end NS_ASSUME_NONNULL_END diff --git a/PatientModel+CoreDataProperties.m b/PatientModel+CoreDataProperties.m index 02d2cb9..2efc1e6 100644 --- a/PatientModel+CoreDataProperties.m +++ b/PatientModel+CoreDataProperties.m @@ -29,5 +29,7 @@ @implementation PatientModel (CoreDataProperties) @dynamic uniqueId; @dynamic weightKg; @dynamic zipCode; +@dynamic healthCardNumber; +@dynamic insuranceGLN; @end