From 85d49e94de6e29fe05b8802895a3f2d53d2c2b92 Mon Sep 17 00:00:00 2001 From: Sebastian Widmer Date: Thu, 13 Apr 2023 18:03:10 +0200 Subject: [PATCH] Add countries mapping --- .goreleaser.yml | 4 + Dockerfile | 1 + api.go | 12 +- .../odoo8/client/model/odoo_composite_id.go | 9 + .../odoo/odoo8/countries/countries.go | 32 + .../odoo/odoo8/countries/countries_test.go | 34 + .../billing/odoostorage/odoo/odoo8/odoo8.go | 40 +- .../odoostorage/odoo/odoo8/odoo8_test.go | 19 + apiserver/billing/odoostorage/odoostorage.go | 4 +- countries-export-full.yaml | 769 ++++++++++++++++++ countries.yaml | 23 + 11 files changed, 926 insertions(+), 21 deletions(-) create mode 100644 apiserver/billing/odoostorage/odoo/odoo8/countries/countries.go create mode 100644 apiserver/billing/odoostorage/odoo/odoo8/countries/countries_test.go create mode 100644 countries-export-full.yaml create mode 100644 countries.yaml diff --git a/.goreleaser.yml b/.goreleaser.yml index 4ab7aaba..075cde74 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -27,6 +27,8 @@ dockers: - "--platform=linux/amd64" image_templates: - "ghcr.io/appuio/control-api:v{{ .Version }}-amd64" + extra_files: + - countries.yaml - goarch: arm64 use: buildx @@ -34,6 +36,8 @@ dockers: - "--platform=linux/arm64/v8" image_templates: - "ghcr.io/appuio/control-api:v{{ .Version }}-arm64" + extra_files: + - countries.yaml docker_manifests: # For prereleases, updating `latest` does not make sense. diff --git a/Dockerfile b/Dockerfile index b28d2103..15cc2b45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,7 @@ RUN \ mkdir /.cache && chmod -R g=u /.cache COPY control-api /usr/local/bin/ +COPY countries.yaml . RUN chmod a+x /usr/local/bin/control-api diff --git a/api.go b/api.go index 8d46681b..156109ee 100644 --- a/api.go +++ b/api.go @@ -19,6 +19,7 @@ import ( "github.com/appuio/control-api/apiserver/authwrapper" billingStore "github.com/appuio/control-api/apiserver/billing" "github.com/appuio/control-api/apiserver/billing/odoostorage" + "github.com/appuio/control-api/apiserver/billing/odoostorage/odoo/odoo8/countries" orgStore "github.com/appuio/control-api/apiserver/organization" "github.com/appuio/control-api/apiserver/secretstorage" "github.com/appuio/control-api/apiserver/user" @@ -57,6 +58,7 @@ func APICommand() *cobra.Command { cmd.Flags().BoolVar(&ob.billingEntityFakeMetadataSupport, "billing-entity-fake-metadata-support", false, "Enable metadata support for the fake storage backend") cmd.Flags().StringVar(&ob.odoo8URL, "billing-entity-odoo8-url", "http://localhost:8069", "URL of the Odoo instance to use for billing entities") cmd.Flags().BoolVar(&ob.odoo8DebugTransport, "billing-entity-odoo8-debug-transport", false, "Enable debug logging for the Odoo transport") + cmd.Flags().StringVar(&ob.odoo8CountryListPath, "billing-entity-odoo8-country-list", "countries.yaml", "Path to the country list file in the format of [{name: \"Germany\", code: \"DE\", id: 81},...]") cmd.Flags().StringVar(&ib.backingNS, "invitation-storage-backing-ns", "default", "Namespace to store invitation secrets in") @@ -79,18 +81,20 @@ func APICommand() *cobra.Command { } type odooStorageBuilder struct { - billingEntityStorage, odoo8URL string + billingEntityStorage, odoo8URL, odoo8CountryListPath string billingEntityFakeMetadataSupport, odoo8DebugTransport bool } func (o *odooStorageBuilder) Build(s *runtime.Scheme, g genericregistry.RESTOptionsGetter) (rest.Storage, error) { - fmt.Printf("Building storage with options: %#v\n", o) - switch o.billingEntityStorage { case "fake": return billingStore.New(odoostorage.NewFakeStorage(o.billingEntityFakeMetadataSupport).(authwrapper.StorageScoper))(s, g) case "odoo8": - return billingStore.New(odoostorage.NewOdoo8Storage(o.odoo8URL, o.odoo8DebugTransport).(authwrapper.StorageScoper))(s, g) + countryIDs, err := countries.LoadCountryIDs(o.odoo8CountryListPath) + if err != nil { + return nil, err + } + return billingStore.New(odoostorage.NewOdoo8Storage(o.odoo8URL, o.odoo8DebugTransport, countryIDs).(authwrapper.StorageScoper))(s, g) default: return nil, fmt.Errorf("unknown billing entity storage: %s", o.billingEntityStorage) } diff --git a/apiserver/billing/odoostorage/odoo/odoo8/client/model/odoo_composite_id.go b/apiserver/billing/odoostorage/odoo/odoo8/client/model/odoo_composite_id.go index ae860724..09be36b2 100644 --- a/apiserver/billing/odoostorage/odoo/odoo8/client/model/odoo_composite_id.go +++ b/apiserver/billing/odoostorage/odoo/odoo8/client/model/odoo_composite_id.go @@ -24,6 +24,15 @@ type OdooCompositeID struct { Name string } +// NewCompositeID creates a new, valid OdooCompositeID. +func NewCompositeID(id int, name string) OdooCompositeID { + return OdooCompositeID{ + Valid: true, + ID: id, + Name: name, + } +} + // UnmarshalJSON handles deserialization of OdooCompositeID. func (t *OdooCompositeID) UnmarshalJSON(b []byte) error { // Odoo returns false (not null) if a field is not set. diff --git a/apiserver/billing/odoostorage/odoo/odoo8/countries/countries.go b/apiserver/billing/odoostorage/odoo/odoo8/countries/countries.go new file mode 100644 index 00000000..d964a5dc --- /dev/null +++ b/apiserver/billing/odoostorage/odoo/odoo8/countries/countries.go @@ -0,0 +1,32 @@ +package countries + +import ( + "os" + + "sigs.k8s.io/yaml" +) + +type Country struct { + ID int `yaml:"id"` + Code string `yaml:"code"` + Name string `yaml:"name"` +} + +func LoadCountryIDs(path string) (map[string]int, error) { + r, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + var countries []Country + if err := yaml.UnmarshalStrict(r, &countries); err != nil { + return nil, err + } + + countryIDs := make(map[string]int, len(countries)) + for _, c := range countries { + countryIDs[c.Name] = c.ID + } + + return countryIDs, nil +} diff --git a/apiserver/billing/odoostorage/odoo/odoo8/countries/countries_test.go b/apiserver/billing/odoostorage/odoo/odoo8/countries/countries_test.go new file mode 100644 index 00000000..f989af44 --- /dev/null +++ b/apiserver/billing/odoostorage/odoo/odoo8/countries/countries_test.go @@ -0,0 +1,34 @@ +package countries + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_LoadCountryIDs(t *testing.T) { + const countriesYAML = ` +- id: 1 + code: BE + name: Belgium +- id: 5 + code: FR + name: France +- id: 6 + code: false # Seen in the export of the Odoo database + name: Kabott +` + + td := t.TempDir() + p := td + "/countries.yaml" + require.NoError(t, os.WriteFile(p, []byte(countriesYAML), 0644)) + + countryIDs, err := LoadCountryIDs(p) + require.NoError(t, err) + require.Equal(t, map[string]int{ + "Belgium": 1, + "France": 5, + "Kabott": 6, + }, countryIDs) +} diff --git a/apiserver/billing/odoostorage/odoo/odoo8/odoo8.go b/apiserver/billing/odoostorage/odoo/odoo8/odoo8.go index f5fc20f1..9c0cc61d 100644 --- a/apiserver/billing/odoostorage/odoo/odoo8/odoo8.go +++ b/apiserver/billing/odoostorage/odoo/odoo8/odoo8.go @@ -49,8 +49,9 @@ var accountingContactUpdateAllowedFields = newSet( "email", ) -func NewOdoo8Storage(odooURL string, debugTransport bool) odoo.OdooStorage { +func NewOdoo8Storage(odooURL string, debugTransport bool, countryIDs map[string]int) odoo.OdooStorage { return &oodo8Storage{ + countryIDs: countryIDs, sessionCreator: func(ctx context.Context) (client.QueryExecutor, error) { return client.Open(ctx, odooURL, client.ClientOptions{UseDebugLogger: debugTransport}) }, @@ -58,6 +59,8 @@ func NewOdoo8Storage(odooURL string, debugTransport bool) odoo.OdooStorage { } type oodo8Storage struct { + countryIDs map[string]int + sessionCreator func(ctx context.Context) (client.QueryExecutor, error) } @@ -162,7 +165,10 @@ func (s *oodo8Storage) Create(ctx context.Context, be *billingv1.BillingEntity) if be == nil { return errors.New("billing entity is nil") } - company, accounting := mapBillingEntityToPartners(*be) + company, accounting, err := mapBillingEntityToPartners(*be, s.countryIDs) + if err != nil { + return fmt.Errorf("failed mapping billing entity to partners: %w", err) + } inflight := uuid.New().String() l = l.WithValues("debug_inflight", inflight) @@ -212,7 +218,11 @@ func (s *oodo8Storage) Update(ctx context.Context, be *billingv1.BillingEntity) return errors.New("billing entity is nil") } - company, accounting := mapBillingEntityToPartners(*be) + company, accounting, err := mapBillingEntityToPartners(*be, s.countryIDs) + if err != nil { + return fmt.Errorf("failed mapping billing entity to partners: %w", err) + } + origCompany, origAccounting, err := s.get(ctx, be.Name) if err != nil { return fmt.Errorf("error fetching billing entity to update: %w", err) @@ -297,21 +307,21 @@ func mapPartnersToBillingEntity(company model.Partner, accounting model.Partner) } } -func mapBillingEntityToPartners(be billingv1.BillingEntity) (company model.Partner, accounting model.Partner) { +func mapBillingEntityToPartners(be billingv1.BillingEntity, countryIDs map[string]int) (company model.Partner, accounting model.Partner, err error) { + countryID, ok := countryIDs[be.Spec.Address.Country] + if !ok { + return company, accounting, fmt.Errorf("unknown country %q", be.Spec.Address.Country) + } + company = model.Partner{ Name: be.Spec.Name, Phone: model.NewNullable(be.Spec.Phone), - Street: model.NewNullable(be.Spec.Address.Line1), - Street2: model.NewNullable(be.Spec.Address.Line2), - City: model.NewNullable(be.Spec.Address.City), - Zip: model.NewNullable(be.Spec.Address.PostalCode), - CountryID: model.OdooCompositeID{ - ID: 256, // TODO(swi): Switzerland is hardcoded for now - Valid: true, - // Name: be.Spec.Address.Country, - Name: "Switzerland", - }, + Street: model.NewNullable(be.Spec.Address.Line1), + Street2: model.NewNullable(be.Spec.Address.Line2), + City: model.NewNullable(be.Spec.Address.City), + Zip: model.NewNullable(be.Spec.Address.PostalCode), + CountryID: model.NewCompositeID(countryID, ""), } company.SetEmails(be.Spec.Emails) @@ -320,7 +330,7 @@ func mapBillingEntityToPartners(be billingv1.BillingEntity) (company model.Partn } accounting.SetEmails(be.Spec.AccountingContact.Emails) - return company, accounting + return company, accounting, nil } func setStaticAccountingContactFields(a *model.Partner) { diff --git a/apiserver/billing/odoostorage/odoo/odoo8/odoo8_test.go b/apiserver/billing/odoostorage/odoo/odoo8/odoo8_test.go index 89f92a8e..504531ae 100644 --- a/apiserver/billing/odoostorage/odoo/odoo8/odoo8_test.go +++ b/apiserver/billing/odoostorage/odoo/odoo8/odoo8_test.go @@ -327,11 +327,30 @@ func TestUpdate(t *testing.T) { }, s) } +func Test_CreateUpdate_UnknownCountry(t *testing.T) { + ctrl, _, subject := createStorage(t) + defer ctrl.Finish() + + s := &billingv1.BillingEntity{ + ObjectMeta: metav1.ObjectMeta{ + Name: "be-702", + }, + Spec: billingv1.BillingEntitySpec{ + Address: billingv1.BillingEntityAddress{ + Country: "Vatican City", + }, + }, + } + require.ErrorContains(t, subject.Create(context.Background(), s), "unknown country") + require.ErrorContains(t, subject.Update(context.Background(), s), "unknown country") +} + func createStorage(t *testing.T) (*gomock.Controller, *clientmock.MockQueryExecutor, *oodo8Storage) { ctrl := gomock.NewController(t) mock := clientmock.NewMockQueryExecutor(ctrl) return ctrl, mock, &oodo8Storage{ + countryIDs: map[string]int{"": 0, "Switzerland": 1, "Germany": 2}, sessionCreator: func(ctx context.Context) (client.QueryExecutor, error) { return mock, nil }, diff --git a/apiserver/billing/odoostorage/odoostorage.go b/apiserver/billing/odoostorage/odoostorage.go index e706eba4..0ba1fadc 100644 --- a/apiserver/billing/odoostorage/odoostorage.go +++ b/apiserver/billing/odoostorage/odoostorage.go @@ -18,9 +18,9 @@ func NewFakeStorage(metadataSupport bool) Storage { } // NewOdoo8Storage returns a new storage provider for BillingEntities -func NewOdoo8Storage(odooURL string, debugTransport bool) Storage { +func NewOdoo8Storage(odooURL string, debugTransport bool, countryIDs map[string]int) Storage { return &billingEntityStorage{ - storage: odoo8.NewOdoo8Storage(odooURL, debugTransport), + storage: odoo8.NewOdoo8Storage(odooURL, debugTransport, countryIDs), } } diff --git a/countries-export-full.yaml b/countries-export-full.yaml new file mode 100644 index 00000000..6a3db69b --- /dev/null +++ b/countries-export-full.yaml @@ -0,0 +1,769 @@ +# Full export from the VSHN Odoo database +- code: AF + id: 3 + name: Afghanistan, Islamic State of +- code: AX + id: 16 + name: Åland Islands +- code: AL + id: 6 + name: Albania +- code: DZ + id: 63 + name: Algeria +- code: AS + id: 12 + name: American Samoa +- code: AD + id: 1 + name: Andorra, Principality of +- code: AO + id: 9 + name: Angola +- code: AI + id: 5 + name: Anguilla +- code: AQ + id: 10 + name: Antarctica +- code: AG + id: 4 + name: Antigua and Barbuda +- code: AR + id: 11 + name: Argentina +- code: AM + id: 7 + name: Armenia +- code: AW + id: 15 + name: Aruba +- code: AU + id: 14 + name: Australia +- code: AT + id: 13 + name: Austria +- code: AZ + id: 17 + name: Azerbaijan +- code: BS + id: 33 + name: Bahamas +- code: BH + id: 24 + name: Bahrain +- code: BD + id: 20 + name: Bangladesh +- code: BB + id: 19 + name: Barbados +- code: BY + id: 37 + name: Belarus +- code: BE + id: 21 + name: Belgium +- code: BZ + id: 38 + name: Belize +- code: BJ + id: 26 + name: Benin +- code: BM + id: 28 + name: Bermuda +- code: BT + id: 34 + name: Bhutan +- code: BO + id: 30 + name: Bolivia +- code: BQ + id: 31 + name: Bonaire, Sint Eustatius and Saba +- code: BA + id: 18 + name: Bosnia-Herzegovina +- code: BW + id: 36 + name: Botswana +- code: BV + id: 35 + name: Bouvet Island +- code: BR + id: 32 + name: Brazil +- code: IO + id: 106 + name: British Indian Ocean Territory +- code: BN + id: 29 + name: Brunei Darussalam +- code: BG + id: 23 + name: Bulgaria +- code: BF + id: 22 + name: Burkina Faso +- code: BI + id: 25 + name: Burundi +- code: KH + id: 117 + name: Cambodia, Kingdom of +- code: CM + id: 48 + name: Cameroon +- code: CA + id: 39 + name: Canada +- code: CV + id: 53 + name: Cape Verde +- code: KY + id: 124 + name: Cayman Islands +- code: CF + id: 41 + name: Central African Republic +- code: TD + id: 216 + name: Chad +- code: CL + id: 47 + name: Chile +- code: CN + id: 49 + name: China +- code: CX + id: 55 + name: Christmas Island +- code: CC + id: 40 + name: Cocos (Keeling) Islands +- code: CO + id: 50 + name: Colombia +- code: KM + id: 119 + name: Comoros +- code: CG + id: 43 + name: Congo +- code: CD + id: 42 + name: Congo, Democratic Republic of the +- code: CK + id: 46 + name: Cook Islands +- code: CR + id: 51 + name: Costa Rica +- code: HR + id: 98 + name: Croatia +- code: CU + id: 52 + name: Cuba +- code: CW + id: 54 + name: Curaçao +- code: CY + id: 56 + name: Cyprus +- code: CZ + id: 57 + name: Czech Republic +- code: DK + id: 60 + name: Denmark +- code: false + id: 255 + name: Deutschland +- code: DJ + id: 59 + name: Djibouti +- code: DM + id: 61 + name: Dominica +- code: DO + id: 62 + name: Dominican Republic +- code: TP + id: 225 + name: East Timor +- code: EC + id: 64 + name: Ecuador +- code: EG + id: 66 + name: Egypt +- code: SV + id: 211 + name: El Salvador +- code: GQ + id: 88 + name: Equatorial Guinea +- code: ER + id: 68 + name: Eritrea +- code: EE + id: 65 + name: Estonia +- code: ET + id: 70 + name: Ethiopia +- code: FK + id: 73 + name: Falkland Islands +- code: FO + id: 75 + name: Faroe Islands +- code: FJ + id: 72 + name: Fiji +- code: FI + id: 71 + name: Finland +- code: FR + id: 76 + name: France +- code: GF + id: 80 + name: French Guyana +- code: TF + id: 217 + name: French Southern Territories +- code: GA + id: 77 + name: Gabon +- code: GM + id: 85 + name: Gambia +- code: GE + id: 79 + name: Georgia +- code: DE + id: 58 + name: Germany +- code: GH + id: 81 + name: Ghana +- code: GI + id: 82 + name: Gibraltar +- code: GR + id: 89 + name: Greece +- code: GL + id: 84 + name: Greenland +- code: GD + id: 78 + name: Grenada +- code: GP + id: 87 + name: Guadeloupe (French) +- code: GU + id: 92 + name: Guam (USA) +- code: GT + id: 91 + name: Guatemala +- code: GG + id: 83 + name: Guernsey +- code: GN + id: 86 + name: Guinea +- code: GW + id: 93 + name: Guinea Bissau +- code: GY + id: 94 + name: Guyana +- code: HT + id: 99 + name: Haiti +- code: HM + id: 96 + name: Heard and McDonald Islands +- code: VA + id: 238 + name: Holy See (Vatican City State) +- code: HN + id: 97 + name: Honduras +- code: HK + id: 95 + name: Hong Kong +- code: HU + id: 100 + name: Hungary +- code: IS + id: 109 + name: Iceland +- code: IN + id: 105 + name: India +- code: ID + id: 101 + name: Indonesia +- code: IR + id: 108 + name: Iran +- code: IQ + id: 107 + name: Iraq +- code: IE + id: 102 + name: Ireland +- code: IM + id: 104 + name: Isle of Man +- code: IL + id: 103 + name: Israel +- code: IT + id: 110 + name: Italy +- code: CI + id: 45 + name: Ivory Coast (Cote D'Ivoire) +- code: JM + id: 112 + name: Jamaica +- code: JP + id: 114 + name: Japan +- code: JE + id: 111 + name: Jersey +- code: JO + id: 113 + name: Jordan +- code: KZ + id: 125 + name: Kazakhstan +- code: KE + id: 115 + name: Kenya +- code: KI + id: 118 + name: Kiribati +- code: KW + id: 123 + name: Kuwait +- code: KG + id: 116 + name: Kyrgyz Republic (Kyrgyzstan) +- code: LA + id: 126 + name: Laos +- code: LV + id: 135 + name: Latvia +- code: LB + id: 127 + name: Lebanon +- code: LS + id: 132 + name: Lesotho +- code: LR + id: 131 + name: Liberia +- code: LY + id: 136 + name: Libya +- code: LI + id: 129 + name: Liechtenstein +- code: LT + id: 133 + name: Lithuania +- code: LU + id: 134 + name: Luxembourg +- code: MO + id: 148 + name: Macau +- code: MK + id: 144 + name: Macedonia, the former Yugoslav Republic of +- code: MG + id: 142 + name: Madagascar +- code: MW + id: 156 + name: Malawi +- code: MY + id: 158 + name: Malaysia +- code: MV + id: 155 + name: Maldives +- code: ML + id: 145 + name: Mali +- code: MT + id: 153 + name: Malta +- code: MH + id: 143 + name: Marshall Islands +- code: MQ + id: 150 + name: Martinique (French) +- code: MR + id: 151 + name: Mauritania +- code: MU + id: 154 + name: Mauritius +- code: YT + id: 248 + name: Mayotte +- code: MX + id: 157 + name: Mexico +- code: FM + id: 74 + name: Micronesia +- code: MD + id: 139 + name: Moldavia +- code: MC + id: 138 + name: Monaco +- code: MN + id: 147 + name: Mongolia +- code: ME + id: 140 + name: Montenegro +- code: MS + id: 152 + name: Montserrat +- code: MA + id: 137 + name: Morocco +- code: MZ + id: 159 + name: Mozambique +- code: MM + id: 146 + name: Myanmar +- code: NA + id: 160 + name: Namibia +- code: NR + id: 169 + name: Nauru +- code: NP + id: 168 + name: Nepal +- code: NL + id: 166 + name: Netherlands +- code: AN + id: 8 + name: Netherlands Antilles +- code: NT + id: 170 + name: Neutral Zone +- code: NC + id: 161 + name: New Caledonia (French) +- code: NZ + id: 172 + name: New Zealand +- code: NI + id: 165 + name: Nicaragua +- code: NE + id: 162 + name: Niger +- code: NG + id: 164 + name: Nigeria +- code: NU + id: 171 + name: Niue +- code: NF + id: 163 + name: Norfolk Island +- code: MP + id: 149 + name: Northern Mariana Islands +- code: KP + id: 121 + name: North Korea +- code: NO + id: 167 + name: Norway +- code: OM + id: 173 + name: Oman +- code: PK + id: 179 + name: Pakistan +- code: PW + id: 186 + name: Palau +- code: PS + id: 184 + name: Palestinian Territory, Occupied +- code: PA + id: 174 + name: Panama +- code: PG + id: 177 + name: Papua New Guinea +- code: PY + id: 187 + name: Paraguay +- code: PE + id: 175 + name: Peru +- code: PH + id: 178 + name: Philippines +- code: PN + id: 182 + name: Pitcairn Island +- code: PL + id: 180 + name: Poland +- code: PF + id: 176 + name: Polynesia (French) +- code: PT + id: 185 + name: Portugal +- code: PR + id: 183 + name: Puerto Rico +- code: QA + id: 188 + name: Qatar +- code: RE + id: 189 + name: Reunion (French) +- code: RO + id: 190 + name: Romania +- code: RU + id: 192 + name: Russian Federation +- code: RW + id: 193 + name: Rwanda +- code: BL + id: 27 + name: Saint Barthélémy +- code: SH + id: 200 + name: Saint Helena +- code: KN + id: 120 + name: Saint Kitts & Nevis Anguilla +- code: LC + id: 128 + name: Saint Lucia +- code: MF + id: 141 + name: Saint Martin (French part) +- code: PM + id: 181 + name: Saint Pierre and Miquelon +- code: ST + id: 210 + name: Saint Tome (Sao Tome) and Principe +- code: VC + id: 239 + name: Saint Vincent & Grenadines +- code: WS + id: 246 + name: Samoa +- code: SM + id: 205 + name: San Marino +- code: SA + id: 194 + name: Saudi Arabia +- code: false + id: 256 + name: Schweiz +- code: SN + id: 206 + name: Senegal +- code: RS + id: 191 + name: Serbia +- code: SC + id: 196 + name: Seychelles +- code: SL + id: 204 + name: Sierra Leone +- code: SG + id: 199 + name: Singapore +- code: SX + id: 212 + name: Sint Maarten (Dutch part) +- code: SK + id: 203 + name: Slovakia +- code: SI + id: 201 + name: Slovenia +- code: SB + id: 195 + name: Solomon Islands +- code: SO + id: 207 + name: Somalia +- code: ZA + id: 250 + name: South Africa +- code: GS + id: 90 + name: South Georgia and the South Sandwich Islands +- code: KR + id: 122 + name: South Korea +- code: SS + id: 209 + name: South Sudan +- code: ES + id: 69 + name: Spain +- code: LK + id: 130 + name: Sri Lanka +- code: SD + id: 197 + name: Sudan +- code: SR + id: 208 + name: Suriname +- code: SJ + id: 202 + name: Svalbard and Jan Mayen Islands +- code: SZ + id: 214 + name: Swaziland +- code: SE + id: 198 + name: Sweden +- code: CH + id: 44 + name: Switzerland +- code: SY + id: 213 + name: Syria +- code: TW + id: 229 + name: Taiwan +- code: TJ + id: 220 + name: Tajikistan +- code: TZ + id: 230 + name: Tanzania +- code: TH + id: 219 + name: Thailand +- code: false + id: 258 + name: The Netherlands +- code: TG + id: 218 + name: Togo +- code: TK + id: 221 + name: Tokelau +- code: TO + id: 224 + name: Tonga +- code: TT + id: 227 + name: Trinidad and Tobago +- code: TN + id: 223 + name: Tunisia +- code: TR + id: 226 + name: Turkey +- code: TM + id: 222 + name: Turkmenistan +- code: TC + id: 215 + name: Turks and Caicos Islands +- code: TV + id: 228 + name: Tuvalu +- code: UG + id: 232 + name: Uganda +- code: UA + id: 231 + name: Ukraine +- code: AE + id: 2 + name: United Arab Emirates +- code: GB + id: 233 + name: United Kingdom +- code: US + id: 235 + name: United States +- code: UY + id: 236 + name: Uruguay +- code: UM + id: 234 + name: USA Minor Outlying Islands +- code: UZ + id: 237 + name: Uzbekistan +- code: VU + id: 244 + name: Vanuatu +- code: VE + id: 240 + name: Venezuela +- code: VN + id: 243 + name: Vietnam +- code: VG + id: 241 + name: Virgin Islands (British) +- code: VI + id: 242 + name: Virgin Islands (USA) +- code: WF + id: 245 + name: Wallis and Futuna Islands +- code: EH + id: 67 + name: Western Sahara +- code: YE + id: 247 + name: Yemen +- code: YU + id: 249 + name: Yugoslavia +- code: ZR + id: 252 + name: Zaire +- code: ZM + id: 251 + name: Zambia +- code: ZW + id: 253 + name: Zimbabwe diff --git a/countries.yaml b/countries.yaml new file mode 100644 index 00000000..041b446e --- /dev/null +++ b/countries.yaml @@ -0,0 +1,23 @@ +- code: CH + id: 44 + name: Switzerland +# Not an error: Someone created a country with the name "Schweiz" in the ERP. +- code: false + id: 256 + name: Schweiz + +- code: LI + id: 129 + name: Liechtenstein +- code: AT + id: 13 + name: Austria +- code: IT + id: 110 + name: Italy +- code: FR + id: 76 + name: France +- code: DE + id: 58 + name: Germany