diff --git a/ServicePoints.csv b/ServicePoints.csv new file mode 100644 index 0000000..ce51731 --- /dev/null +++ b/ServicePoints.csv @@ -0,0 +1,159 @@ +Kottawa,Senaro Motor Company (Pvt) Ltd,"360/1,Highlevel Road,Kottawa",0117 700700 +Agunukolapalassa,Nimal Motors,"No.246,Hungama Road,Agunukolapalassa",712912035 +Akkarapattu,Kathis Motors,"Drainage Road,Kolavil 03,Akkarapatthuwa",770850904 +Akuressa,Joe Brothers,"No.27/A,Akuressa Road,Matara",0412226841/0714336510 +Aluthgama,Road Star,"317/1/A,Galle Road, Aluthgama",766612349 +Ambalangoda,Kosala Motors (Lak Motors),"No 5/A,Wikrama Suriya Road,Ambalangoda",773759661/0714644444 +Ambalangoda,Southern Motors,"No 245 /J , Sithum sewana,Thalgasgoda,Ambalangoda",779627451/0722044444 +Ambalanthota,KBS Isuru Motors,"Dehigahalanda ,Ambalanthota",775355696/0776447656 +Ambalanthota,Samagi Motors,"Thangalla Road, Pawaluwila, Ambalanthota ",772688894 +Ampara,Chinthaka Service Center,"KP/46/5/A Kalmunai Road,Ampara",776532544 +Anamaduwa,Chandana Motors,"Putthlam Road ,Anamaduwa",776207436 +Anuradhapura,Sarath Motors Engineering & Ayan Works,"School Lane,Bulnewa.",252269420/713659420 +Anuradhapura,Neel Motors,"No.334/107,FGI Lane,Bank Side,Anuradhapura.",775717909 +Anuradhapura,MD Motors & Service,"Infront of Filing Station,Sevanapitiya.",783565173 +Anuradhapura,Priyantha Motors,"Kurunegala Road,Thalawa.",711036309 +Anuradhapura,Pathum Motors,"Dahaiyagma Junction,Anuradhapura",777725222/0707305207 +Aralaganwila,New Udaya Motors,"No.91,Old Hospital Junction,Aralaganwila.",783947612 +Aralaganwila,Jagath Service Center,"No 75,Tank Side,Aralaganvila,Polonnaruwa",785604941 +Aralaganwila,Wijitha Motors,"No.80,Bogas Handiya,Aralaganwila",0771075080/0741892129 +Avissawella,Charith Salinda Motors,"182/A,Colombo Road,Ukwattha,Avissawella",0777397052/0362233885 +Avissawella,Hesara Auto Motors,"A/71/1/1A/Rathnapura Road,Madola,Avissawella.",0722970863/0767187894 +Baddegama,Buddhi Cycle Works,"No 13,Isuru Uyana,Idurupathwila,Baddegama",779077543/0775140543 +Badulla,Super Speed Masters,"Passara Road ,Badulla",0552222930/0779261655 +Bandaragama,Deepthi Motors,"Wevita,Bandaragama",776236756 +Bandarawela,Janodhya Enterprises,"256,Thanthiriya,Bandarawela",0572231738/0773599551 +Balangoda,Kelum Motors,"No 59 /G, Thumbagoda,Balangoda ",0706904000/777254000 +Batapola,P C Center,"Kahawabatapola road,Nindana",770418733 +Batticoloa,Yamaha Service Center,"568A,Trinco road,Batticoloa",773268484 +Batticoloa,GN Motors,"560/C,Trinco Road,Urani,Batticaloa",777739691 +Bingiriya,Semini Motors,"Mukalana Hena,Bingiriya.",776120942 +Biyagama,Dinesh Motors,"No.241/B,kottunna Road,Biyagama",752468488 +Butthala,Anura Motors,"Monaragala Road,Butthala",776821336 +Chavakachchri,SM Service,"177/A,Kandy road,Chavakachcheri",778994623 +Dambulla,New Ajith Motors,"605/C/A,Kurunegala Road,Pohoranwewa,Dambulla.",716081369/767153147 +Dambulla,Nishshanka Motors,"kandalama road,Dambulla",776870632 +Dehiattakandiya,Nipun Motors,"1/A,Nawagama,Dehiattakandiya",784780660 +Dehiowita,Priyantha Tvs,"Main Street, Dehiowita",777677491 +Digana,Mahanuwara Motors,"No 89 , Kandy Road, Kengalla",773428962 +Divulapitiya,Rangana Motors,"90/1,Thammita,Hunumulla,Divulapitiya",783896535 +Eheliyagoda,Y N Motors,"No.A/29/1,Rangegama,Panawala,Eheliyagoda",0765290215/0717305116 +Eheliyagoda,Rathna Motors,"No 70/B,Thoranakada,Eheliyagoda",724395682 +Elpitiya,Kumara Motors,"Ambalangoda road,Elpitiya",770361454 +Eppawala,Thisaru Motors,"No.323/A,Palugaswewa,Eppawala.",0711030677/0701940043 +Embilipitiya,Dewmi Motors,"Thalawa South,Kariyamadiththa",717424045 +Embilipitiya,Kamal Auto Motors,"83/3,Pinbaduyaya Road,New Town,Embilipitiya",777937720 +Embilipitiya,3D Motors,"Pallegama,Embilipitiya",767860664 +Galenbidunuwewa,Yasa Isuru Motors,"Kekirawa Road,Galenbidunawewa",717422999 +Galle,Ariyarathna Motors,"No.109,Heenpandala Road,Galle.",0764234042/0779833733 +Galle,Mahanama Enterprises,"No 7, Petigalawatta,2nd Cross Road, Galle",912248954/0777564494/0777935378 +Galmuruwa,Bhasita Motors,"Galmuruwa,Galmuruwa",077 8073683 +Gampaha,Ajantha Motors,"243,Batapotha,Madhelgamuwa,Gampaha",0774492686/0715159922 +Gampola,Viyan Motors,"Gampolawela Road,Gampola",777045973 +Gampola,Aruna Motors,"No 180 ,Kandy Road,Gampola",777101988 +Ganemulla,Wasantha Motor house,"No,4/3 Katuwalamulla,Ganemulla",0711550505/0776591710 +Galgamuwa,Sampath Motors,"No 101/ A , High Street , Galgamuwa",776252139 +Hanwella,Sasiru Motors,"No.59/1,Kaluaggala,Hanwella",0362255116/0716053960 +Halawatha (chillaw),Kenuja Motors,"Temple Junction, Mahawewa, Chillaw",771346817 +Hatton,Royal Motors,"No 154, Dickoya Road,Hatton",770188066/0776696606 +Hingurana,Jaya Sri Motors,"No 39 ,Eragama Road,Higurana",0763903047/717923190 +Horowpothana,Senith Motors,"Oppersid People's Bank,Horowpathana.",0711251600/0761055329 +Horana,Saranga Motors,"No.16,Wawulugala Road,Munagama,Horana",772849507 +Ingiriya,Chanaka Motors,"Madala,Ellagawa,Ingiriya",0786347178/0773727941 +Jaffna,Rajani Motors,"No.249,Temple Road,Nallur, Jaffna",778690475 +Jaffna,Green World Car Wash,"Palali Road,Urumpirai(Chunnakam)",773011547 +Jaffna,Kavi Service Center,"No 121/38, Punnalai Road,Koonanthoddam,Jaffana",0774017037/0772761982 +Kadawatha,Chandima Motors," No.348/18, Rajasinghe Mw,Ihala Karagahamuna,Kadwatha",711458901 +Kahatagasdigiliya,Thakshilu Motor & Service,"Kuda kulumwakada,Kahatagasdigiliya",255636452 +Kalmunai,Ashok Motors,"Batticaloa Road, Kalmunai - 01",0752855680/757572447 +Kalawana,Jayasundara Motors,"Rathnapura Road,Near The Black Bridge,Kalawana.",777550123 +Kalawana,Ashoka Auto Electricals,"Rathnapura Road,Kalawana",0776756911/0743116911 +Kaluthara,Dilhan Motor Cyclees,"No.366/2,Galle Road,Kaluthra North.",754894528 +Kamburupitiya,Rajeeka Industries,"Mulatiyana Road,Kamburupitiya",717518442 +Kantale,Kenuk Enterprises,"No 166/2, A , In front of Dheewaragama,Kantale",0711777322/0771233232/0262235322 +Kandana,Jude Motors,"17/1,Circular Road,Kandana",718331124 +Kandy,Nishantha Enterprises,"No.31,Poornawtta,kandy",777802284 +Kandy,Chuti Auto Paint,"No 74/1 A,KPH Hospital Road,Mapanawathura Lane,Kandy",0776663238/0752625281 +Kebitigollewa,Samantha Motors,"Horowpothana Road,Kebithigollewa",777760128 +Kegalle,Rohan Motors,"178/A,Ranwala,Kegalle",715616490 +Kekirawa,Indika Motors,"Dambulla Road,Mahakekirawa,Kekirawa.",777247948 +Kekirawa,New Central Super Service,"50/5,Main street,Kekirawa",766257433 +Kilinochchi,Arasan Motors,"Near the Kandasamy Kovil,Kandy Road,Kilinochchi",077 7766901 +Kinniya,A J M Motors,"Ward no 05, Kinniya",779677345 +Kiridiwela,Ashmini Service,"Hanwella - Urapola Rd,Kirindiwela ",773428842 +Kiridiwela,S.P.Garage & Premium,"37/3,Ihalagama,Kiridiwela",0777145753/0770795497 +Kithulgala,Chandana Motors,"Colombo Road,Kithulgala",778440071 +Kokkadicholai,Asher Work Shop,"Main Street,Kokadicholai",773881958 +Kotikawatta,Malinda Twowheelers,"340/2,Avissawella Road,Kelanimulla,Angoda",717974741/0767974741 +Kuliyapitiya,Sudath Motors,"Narammala road,Bohingamuwa, kuliyapitiya",703231982 +Kurunegala,Sehan Motors & Service,"No.395/1,Nagolla,Hettipola.",705561583/717161583 +Kurunegala,Some Motors,"Polatthapitiya,Kurunegala",0779263731/725396707 +Kurunegala,Jayalath Motors,"Suhada Mw,Wadhuraagala,Kurunegala",776063823 +Mahawa,Gayan Motors,"Ullalapola,Daladagama,Mahawa",0789857797/0728385133 +Mahiyanganaya,Yamaha Center,"No 100/10/A , Badulla Road, Mahiyanganaya",0714912896 / 0552257555 +Maggona,Madusha Service Center,"285 A,Galle Road,Maggona",773076947 +Malabe,P N P Motors,"No 417/3Athurugireya Rd,Malabe",714941549 +Manampitiya,Auto Mart Service Center,"Main Street,Mannampitiya",776169047 +Mannar,Tharmika Motors,"Hospital Road, Sinnakadai, Mannar ",772624156 +Matale,Kumar Motors,"201,Ratthota Road,Kaludewala,Matale",774412037 +Matale,New Mahesh Motors,"42,Kumbiyangoda,Matale",771717121 +Matara ,Munasinghe Motors,"No 26, Rathmale Road,Kekanadhura Handiya,Matara",712493748 +Matara ,Riyon Motors,"No.27/A,Akuressa Road,Nupe,Matara",0714955836/042226841 +Mathugama,Gamini Motors,"Kaluthara Road,Panthiya,Mathugama",772016463 +Mawathagama,Ravindra Motors,"9th Pillar, Illukwela,Weuda,Mawathgama",774085307 +Madawachchiya,Jayasuriya Motors,"Galenbandawewa,Sangilikandarawa,Medawachchiya.",0719818662/0710140455 +Medirigiriya ,Akitha Motors & Service,"Near the Police Station,Mandalagiriya, Medirigiriya.",741999439 +Middeniya,Madara Service Center,"Weerakatiya Road,Middeniya",770676692 +Minuwangoda,Devli Motors,"242/C,Nilpanagoda Junction,Minuwangoda",0775219998//0751492918 +Monaragala,Kumara Motors,"318,pothuwil road,Monaragala",0775968127/0721310321 +Moratuwa,Saman Motors,"31/A,Dharmarathna Road,Rawathawatta,Moratuwa",783701711 +Narammala,Abisheka Motors,"Kurunegala road,Ganegoda ,Narammala",743668074 +Nathandiya,Jayasiri Motors,"Udubadhawa Road,Nattandiya",077770115/777855825 +Nawalapitiya,Binu Motors,"No 588/1, Mawela Road,Ulapane ",0757584748/0756891811 +Negombo ,Janaka Motors,"Rajapaksha Road We,Negombo",772846650 +Negombo ,Anton Auto Works & Service ,"186/C,Y Junction, Negombo",776332942 +Nikawaratiya,Susantha Motors,"Maho Road,Nikawaratiya",771595390 +Norochchole,Yamaha Ris World ,"Kalpitiya Road,Norochchola,Poolachchena",0322268858/0719188777 +Nuwara Eliya,Iresh Motors,"Udupussellawa Road,Hawaeliya,Nuwara Eliya",767913151 +Opanayaka,Dayananda Motors,"Galhira Janction,Opanayaka.",778078681 +Panadura,Botheju Motors,"336,DMA Cooray lane,walana,panadura",382256766/0713743285 +Panadura,Sri Kanth Auto Garaj,"393/A/1,Old Galle Road, Gorakana,Panadura",0382297113/0774328172 +Panadura,Niro Enterprises ,"No 62/A,Galle Road,Pinwatta, Panadura",787990050 +Panchikawatte,Arrow Motors,"J-6,Jayantha Weerasekara Mw,Colombo 10",773490167 +ParakramaPura,Thushara Motors,"4 Mile post,Padavi Parakramapura",0768501461/715569466 +Pelmadulla,Rupasingha Motors,"Gankanda,School Road,Pelmadulla",766143397/0769222602 +Piliyandala,Uvidu Motors,"127/A,Horana Road,Kesbewa,Piliyandala",771747031 +Pitabeddara,Sampath Motors,"Athuela ,Pitabeddara.",767692225 +Pitigala,Kumara Motors ,"Dickhena,New town,Pitigala",777498530 +Pitigala,Jagath Motors & Service,"Mapalagama Road,Mathaka ,Pitigala",766560815 +Polgahawela,Anura Motors,"25, New Colombo Road,Polgahawela ",784229563 / 0763937160 +Polgasowita,Tashini Motors,"No.221/E,Siyabalagoda,Polgasowita.",776367858 +Polonnaruwa,Naween Motors,"Minnariya Road,Hingurakgoda",779050948 +Polonnaruwa,United Bolts,"Bendiwewa,Polonnaruwa",770880090 +Polpitigama,Sahiru Motors,"Main Road,Polpitigama",0771328311/0764242196 +Puthukkudiyiruppu,K.M.S.Service Station,"No.9,Malligaitivu,Puthukkudiyiruppu.",778675720 +Rathnapura,Sri Ramya Motors,"Batugedara,Rathnapura",717158156 +Rathnapura,Dinesh Motors,"446,colombo Road,Rathnapura",712884304 +Rathnapura,Dabare Motors,"No.185/2,Gatangama,Rathnapura.",719617239 +Rathnapura,L M Auto Works,"No 125/4, Near The Samanala Dewalaya , Rathnapura",0452230599/0702525599 +Samanthurai,Mohammed Motors,"No.77,Main Street, Sammanthurai",0771801000/0757305302 +Samanthurai,A H Sitteek Garaj,"Ampara Road, Samanthurai",777508771 +Sevanapitiya,Kumara Motors,"Susirigama, Veliikanda Road,Sevanapitiya.",0764449324/0781682020 +Thalawakele,Rudra Motors,"No 184/3,Hatton Road, Thalawakele",771327763 +Teldeniya,Nalaka Super Service,"Madapola ,Teldeniya",768405604 +Thambuththegama,Sumith Motors,"151,Anuradhapura Road,Rajina junction,Thambuttegama",771905278/0761544135 +Thissamaharama,Janahitha Motors & Industrial,"No.151,Hambanthota Road,Thissamaharama",719643424 +Trincomalee,Akram Motors,"No 59/11, Jamaliya Street, Trincomalee",757197924 +Udugama,New Palitha Motors,"Kurunda Road,Udugama",777411857 +Urubokka,Sandaru Motors,"413/C,Ruwansewana Govipala,Urubokka",711727596 +Urubokka,Gunawardena Enterprises,"Main Street,Urubokka",775359000 +Vauniya,R D N Motors,"Kandy road,Vauniya",779762599 +Walasmulla,Ravindu Motors,"Hakmana,Muduna,Walasmulla",766378478 +Warakapola,Rathnayaka Motors,"Thuba liyadha ,Warakapola",0760915811/0773175940/0352269115 +Wariyapola,Ranaweera Motors,"Chillaw Road,Wariyapola",778592995 +Wattala,Demak Lanka ,"No 165 , Church Cross Lane, Wattala",117711211 +Welimada,Chandana Enterprises,"Gallahara,Lakahettiya.",763445135 +Welimada,Gihan Motors,"Bandarawela road,Welimada",777288913 +Weliweriya,Chaminda Motors,"145/19/C,New Kandy Road,Weliweriya",768871611 +Wellawaya,Piyal Auto Engineering,"Nugayaya , Wellawaya",765390498 +Yakkala,Herath TVS,"Kandy road,Yakkala",718390315 \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..f80c971 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -7,6 +7,12 @@ # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. +analyzer: + exclude: + - build/** + - android/** + - ios/** + - web/** include: package:flutter_lints/flutter.yaml linter: diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 6f0010a..7f90b3f 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -1,6 +1,5 @@ plugins { id("com.android.application") - id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } @@ -11,12 +10,8 @@ android { ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - - kotlinOptions { - jvmTarget = JavaVersion.VERSION_11.toString() + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } defaultConfig { @@ -39,6 +34,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + flutter { source = "../.." } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 238b196..021828b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + + + + + + + + + + + + + Gradle Configuration Cache + + + +
+ +
+ Loading... +
+ + + + + + diff --git a/android/gradle.properties b/android/gradle.properties index f018a61..475a628 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,7 @@ org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index ac3b479..a20f2c4 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index fb605bc..b28021a 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,8 +19,8 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.9.1" apply false - id("org.jetbrains.kotlin.android") version "2.1.0" apply false + id("com.android.application") version "9.1.0" apply false + id("org.jetbrains.kotlin.android") version "2.4.0" apply false } include(":app") diff --git a/pubspec.lock b/pubspec.lock index d863ca5..475ae71 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" clock: dependency: transitive description: @@ -236,26 +236,26 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.20" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.19.0" mime: dependency: transitive description: @@ -409,10 +409,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11" url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.12" typed_data: dependency: transitive description: @@ -425,10 +425,10 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "92b9910f66ed1057fd4da7b040ae7c74cafacf885bdc81be496928d5049b032d" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.3" vm_service: dependency: transitive description: @@ -462,5 +462,5 @@ packages: source: hosted version: "1.1.0" sdks: - dart: ">=3.9.2 <4.0.0" + dart: ">=3.11.0-0 <4.0.0" flutter: ">=3.35.6" diff --git a/scripts/import_service_points.py b/scripts/import_service_points.py new file mode 100644 index 0000000..9895147 --- /dev/null +++ b/scripts/import_service_points.py @@ -0,0 +1,406 @@ +#!/usr/bin/env python3 +"""Import ServicePoints.csv through the same API the app uses to add a point. + +Login (staff, same as the app): + POST {base}/auth/staff-login + {"phone": "...", "password": "..."} + +Create (same body as ServicePointFormScreen): + POST {base}/service-points + {"name", "district", "address", "phone", "notes"} + Authorization: Bearer + +The CSV has no header. Columns are town, name, address, phone. +`district` must be one of the names in lib/labels.dart (sriLankaDistricts). +The spreadsheet town is stored in `notes` so the finer location is kept. + +Examples: + python3 scripts/import_service_points.py --dry-run + python3 scripts/import_service_points.py --phone 07XXXXXXXX --password '***' + SENARO_TOKEN=eyJ... python3 scripts/import_service_points.py +""" + +from __future__ import annotations + +import argparse +import csv +import getpass +import json +import os +import sys +import urllib.error +import urllib.request +from collections import Counter +from pathlib import Path + +DEFAULT_BASE_URL = "https://snrodealer.playpoolstudios.com/api" +ROOT = Path(__file__).resolve().parent.parent + +# Official district names, matching lib/labels.dart. +DISTRICTS = ( + "Ampara", + "Anuradhapura", + "Badulla", + "Batticaloa", + "Colombo", + "Galle", + "Gampaha", + "Hambantota", + "Jaffna", + "Kalutara", + "Kandy", + "Kegalle", + "Kilinochchi", + "Kurunegala", + "Mannar", + "Matale", + "Matara", + "Moneragala", + "Mullaitivu", + "Nuwara Eliya", + "Polonnaruwa", + "Puttalam", + "Ratnapura", + "Trincomalee", + "Vavuniya", +) + +# Spreadsheet town (normalized) -> official district. +TOWN_TO_DISTRICT = { + # Ampara + "akkarapattu": "Ampara", + "ampara": "Ampara", + "dehiattakandiya": "Ampara", + "hingurana": "Ampara", + "kalmunai": "Ampara", + "samanthurai": "Ampara", + # Anuradhapura + "anuradhapura": "Anuradhapura", + "eppawala": "Anuradhapura", + "galenbidunuwewa": "Anuradhapura", + "horowpothana": "Anuradhapura", + "kahatagasdigiliya": "Anuradhapura", + "kebitigollewa": "Anuradhapura", + "kekirawa": "Anuradhapura", + "madawachchiya": "Anuradhapura", + "parakramapura": "Anuradhapura", + "thambuththegama": "Anuradhapura", + # Badulla + "badulla": "Badulla", + "bandarawela": "Badulla", + "mahiyanganaya": "Badulla", + "welimada": "Badulla", + # Batticaloa + "batticoloa": "Batticaloa", + "kokkadicholai": "Batticaloa", + # Colombo + "avissawella": "Colombo", + "hanwella": "Colombo", + "kotikawatta": "Colombo", + "kottawa": "Colombo", + "malabe": "Colombo", + "moratuwa": "Colombo", + "panchikawatte": "Colombo", + "piliyandala": "Colombo", + "polgasowita": "Colombo", + # Galle + "ambalangoda": "Galle", + "baddegama": "Galle", + "batapola": "Galle", + "elpitiya": "Galle", + "galle": "Galle", + "pitigala": "Galle", + "udugama": "Galle", + # Gampaha + "biyagama": "Gampaha", + "divulapitiya": "Gampaha", + "gampaha": "Gampaha", + "ganemulla": "Gampaha", + "kadawatha": "Gampaha", + "kandana": "Gampaha", + "kiridiwela": "Gampaha", + "minuwangoda": "Gampaha", + "negombo": "Gampaha", + "wattala": "Gampaha", + "weliweriya": "Gampaha", + "yakkala": "Gampaha", + # Hambantota + "agunukolapalassa": "Hambantota", + "ambalanthota": "Hambantota", + "middeniya": "Hambantota", + "thissamaharama": "Hambantota", + "walasmulla": "Hambantota", + # Jaffna + "chavakachchri": "Jaffna", + "jaffna": "Jaffna", + # Kalutara + "aluthgama": "Kalutara", + "bandaragama": "Kalutara", + "horana": "Kalutara", + "ingiriya": "Kalutara", + "kaluthara": "Kalutara", + "maggona": "Kalutara", + "mathugama": "Kalutara", + "panadura": "Kalutara", + # Kandy + "digana": "Kandy", + "gampola": "Kandy", + "kandy": "Kandy", + "nawalapitiya": "Kandy", + "teldeniya": "Kandy", + # Kegalle + "dehiowita": "Kegalle", + "kegalle": "Kegalle", + "kithulgala": "Kegalle", + "warakapola": "Kegalle", + # Kilinochchi + "kilinochchi": "Kilinochchi", + # Kurunegala + "bingiriya": "Kurunegala", + "galgamuwa": "Kurunegala", + "kuliyapitiya": "Kurunegala", + "kurunegala": "Kurunegala", + "mahawa": "Kurunegala", + "mawathagama": "Kurunegala", + "narammala": "Kurunegala", + "nikawaratiya": "Kurunegala", + "polgahawela": "Kurunegala", + "polpitigama": "Kurunegala", + "wariyapola": "Kurunegala", + # Mannar + "mannar": "Mannar", + # Matale + "dambulla": "Matale", + "matale": "Matale", + # Matara + "akuressa": "Matara", + "kamburupitiya": "Matara", + "matara": "Matara", + "pitabeddara": "Matara", + "urubokka": "Matara", + # Moneragala + "butthala": "Moneragala", + "monaragala": "Moneragala", + "wellawaya": "Moneragala", + # Mullaitivu + "puthukkudiyiruppu": "Mullaitivu", + # Nuwara Eliya + "hatton": "Nuwara Eliya", + "nuwara eliya": "Nuwara Eliya", + "thalawakele": "Nuwara Eliya", + # Polonnaruwa + "aralaganwila": "Polonnaruwa", + "manampitiya": "Polonnaruwa", + "medirigiriya": "Polonnaruwa", + "polonnaruwa": "Polonnaruwa", + "sevanapitiya": "Polonnaruwa", + # Puttalam + "anamaduwa": "Puttalam", + "galmuruwa": "Puttalam", + "halawatha (chillaw)": "Puttalam", + "nathandiya": "Puttalam", + "norochchole": "Puttalam", + # Ratnapura + "balangoda": "Ratnapura", + "eheliyagoda": "Ratnapura", + "embilipitiya": "Ratnapura", + "kalawana": "Ratnapura", + "opanayaka": "Ratnapura", + "pelmadulla": "Ratnapura", + "rathnapura": "Ratnapura", + # Trincomalee + "kantale": "Trincomalee", + "kinniya": "Trincomalee", + "trincomalee": "Trincomalee", + # Vavuniya + "vauniya": "Vavuniya", +} + + +class ApiError(Exception): + def __init__(self, status: int, message: str): + super().__init__(f"HTTP {status}: {message}") + self.status = status + self.message = message + + +def normalize(value: str) -> str: + return " ".join(value.strip().casefold().split()) + + +def request_json(method: str, url: str, body: dict | None = None, token: str | None = None) -> dict: + data = None if body is None else json.dumps(body).encode() + headers = {"Accept": "application/json", "Content-Type": "application/json"} + if token: + headers["Authorization"] = f"Bearer {token}" + request = urllib.request.Request(url, data=data, headers=headers, method=method) + try: + with urllib.request.urlopen(request, timeout=30) as response: + raw = response.read().decode() + return json.loads(raw) if raw else {} + except urllib.error.HTTPError as error: + raw = error.read().decode() + message = raw or str(error.reason) + try: + payload = json.loads(raw) + except json.JSONDecodeError: + payload = None + if isinstance(payload, dict) and isinstance(payload.get("message"), str): + message = payload["message"] + raise ApiError(error.code, message) from error + + +def load_rows(path: Path) -> list[dict]: + rows: list[dict] = [] + unmapped: list[tuple[int, str]] = [] + with path.open(newline="", encoding="utf-8-sig") as handle: + for line_no, record in enumerate(csv.reader(handle), start=1): + if not record or all(not cell.strip() for cell in record): + continue + if len(record) != 4: + raise SystemExit(f"{path}:{line_no}: expected 4 columns, got {len(record)}") + town, name, address, phone = (cell.strip() for cell in record) + district = TOWN_TO_DISTRICT.get(normalize(town)) + if district not in DISTRICTS: + unmapped.append((line_no, town)) + continue + if not name: + raise SystemExit(f"{path}:{line_no}: missing service point name") + rows.append( + { + "line": line_no, + "town": town, + "body": { + "name": name, + "district": district, + "address": address, + "phone": phone, + "notes": town, + }, + } + ) + if unmapped: + print("Unmapped towns (add them to TOWN_TO_DISTRICT before importing):", file=sys.stderr) + for line_no, town in unmapped: + print(f" line {line_no}: {town}", file=sys.stderr) + raise SystemExit(2) + return rows + + +def point_key(name: str, district: str, phone: str) -> tuple[str, str, str]: + return (normalize(name), district, normalize(phone)) + + +def existing_keys(base_url: str, token: str) -> set[tuple[str, str, str]]: + payload = request_json("GET", f"{base_url}/service-points", token=token) + items = payload.get("data", payload) + if not isinstance(items, list): + raise SystemExit("Unexpected /service-points response; expected a data list") + keys: set[tuple[str, str, str]] = set() + for item in items: + if not isinstance(item, dict): + continue + keys.add( + point_key( + str(item.get("name") or ""), + str(item.get("district") or ""), + str(item.get("phone") or ""), + ) + ) + return keys + + +def login(base_url: str, phone: str, password: str) -> str: + payload = request_json( + "POST", + f"{base_url}/auth/staff-login", + {"phone": phone, "password": password}, + ) + token = payload.get("token") + if not isinstance(token, str) or not token: + raise SystemExit("Staff login succeeded but no token was returned") + user = payload.get("user") if isinstance(payload.get("user"), dict) else {} + name = user.get("name") or user.get("phone") or phone + print(f"Signed in as {name}") + return token + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--csv", type=Path, default=ROOT / "ServicePoints.csv", help="CSV path") + parser.add_argument("--base-url", default=os.environ.get("SENARO_API_BASE_URL", DEFAULT_BASE_URL)) + parser.add_argument("--phone", default=os.environ.get("SENARO_PHONE"), help="Staff phone") + parser.add_argument("--password", default=os.environ.get("SENARO_PASSWORD"), help="Staff password") + parser.add_argument("--token", default=os.environ.get("SENARO_TOKEN"), help="Existing bearer token") + parser.add_argument("--dry-run", action="store_true", help="Print payloads and do not call the API") + parser.add_argument( + "--no-skip-existing", + action="store_true", + help="Create a point even when the same name, district, and phone already exist", + ) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + base_url = args.base_url.rstrip("/") + rows = load_rows(args.csv) + counts = Counter(row["body"]["district"] for row in rows) + print(f"{len(rows)} service points in {args.csv}") + for district in DISTRICTS: + if counts[district]: + print(f" {district}: {counts[district]}") + + if args.dry_run: + print() + for row in rows: + body = row["body"] + print(f"{body['district']}\t{row['town']}\t{body['name']}\t{body['phone']}") + print("\nDry run only. Nothing was sent.") + return + + token = args.token + if not token: + phone = args.phone or input("Staff phone: ").strip() + password = args.password or getpass.getpass("Staff password: ") + if not phone or not password: + raise SystemExit("Staff phone and password are required") + token = login(base_url, phone, password) + + known: set[tuple[str, str, str]] = set() + if not args.no_skip_existing: + known = existing_keys(base_url, token) + print(f"{len(known)} service points already on the server") + + created = 0 + skipped = 0 + failed: list[str] = [] + for row in rows: + body = row["body"] + key = point_key(body["name"], body["district"], body["phone"]) + label = f"line {row['line']}: {body['name']} ({body['district']})" + if key in known: + skipped += 1 + print(f"skip {label}") + continue + try: + request_json("POST", f"{base_url}/service-points", body, token=token) + except ApiError as error: + failed.append(f"{label}: {error.message}") + print(f"fail {label}: {error.message}", file=sys.stderr) + continue + except urllib.error.URLError as error: + failed.append(f"{label}: {error.reason}") + print(f"fail {label}: {error.reason}", file=sys.stderr) + continue + known.add(key) + created += 1 + print(f"added {label}") + + print(f"\nAdded {created}, skipped {skipped}, failed {len(failed)}") + if failed: + raise SystemExit(1) + + +if __name__ == "__main__": + main()