Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
weboob
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
132
Issues
132
List
Boards
Labels
Milestones
Merge Requests
40
Merge Requests
40
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
weboob
weboob
Commits
22fbea5b
Commit
22fbea5b
authored
May 27, 2019
by
Florian Duguet
Committed by
Romain Bignon
Jul 27, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[ameli] total delete
parent
cfd368e8
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
0 additions
and
499 deletions
+0
-499
__init__.py
modules/ameli/__init__.py
+0
-24
browser.py
modules/ameli/browser.py
+0
-126
module.py
modules/ameli/module.py
+0
-80
pages.py
modules/ameli/pages.py
+0
-238
test.py
modules/ameli/test.py
+0
-31
No files found.
modules/ameli/__init__.py
deleted
100644 → 0
View file @
cfd368e8
# -*- coding: utf-8 -*-
# Copyright(C) 2013-2015 Christophe Lampin
#
# This file is part of a weboob module.
#
# This weboob module is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This weboob module is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from
.module
import
AmeliModule
__all__
=
[
'AmeliModule'
]
modules/ameli/browser.py
deleted
100644 → 0
View file @
cfd368e8
# -*- coding: utf-8 -*-
# Copyright(C) 2013-2015 Christophe Lampin
#
# This file is part of a weboob module.
#
# This weboob module is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This weboob module is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from
__future__
import
unicode_literals
from
weboob.browser
import
LoginBrowser
,
URL
,
need_login
from
weboob.exceptions
import
BrowserIncorrectPassword
,
ActionNeeded
from
.pages
import
(
LoginPage
,
HomePage
,
CguPage
,
AccountPage
,
LastPaymentsPage
,
PaymentsPage
,
PaymentDetailsPage
,
Raw
,
UnavailablePage
,
)
from
weboob.tools.compat
import
basestring
__all__
=
[
'AmeliBrowser'
]
class
AmeliBrowser
(
LoginBrowser
):
BASEURL
=
'https://assure.ameli.fr'
loginp
=
URL
(
r'/PortailAS/appmanager/PortailAS/assure\?.*_pageLabel=as_login_page'
,
LoginPage
)
homep
=
URL
(
r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_accueil_page'
,
HomePage
)
cgup
=
URL
(
r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_conditions_generales_page'
,
CguPage
)
accountp
=
URL
(
r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_info_perso_page'
,
AccountPage
)
paymentsp
=
URL
(
r'/PortailAS/appmanager/PortailAS/assure\?_nfpb=true&_pageLabel=as_paiements_page'
,
PaymentsPage
)
paymentdetailsp
=
URL
(
r'/PortailAS/paiements.do\?actionEvt=chargerDetailPaiements.*'
,
PaymentDetailsPage
)
lastpaymentsp
=
URL
(
r'/PortailAS/paiements.do\?actionEvt=afficherPaiements.*'
,
LastPaymentsPage
)
pdf_page
=
URL
(
r'PortailAS/PDFServletReleveMensuel.dopdf\?PDF.moisRecherche=.*'
,
Raw
)
unavailablep
=
URL
(
r'/vu/INDISPO_COMPTE_ASSURES.html'
,
UnavailablePage
)
def
do_login
(
self
):
self
.
logger
.
debug
(
'call Browser.do_login'
)
# set this cookie to get login form in response
self
.
session
.
cookies
[
'hbc'
]
=
''
self
.
loginp
.
stay_or_go
()
if
self
.
homep
.
is_here
():
return
True
self
.
page
.
login
(
self
.
username
,
self
.
password
)
error
=
self
.
page
.
is_error
()
if
error
:
raise
BrowserIncorrectPassword
(
error
)
self
.
page
.
locate_to_cgu_page
()
if
self
.
cgup
.
is_here
():
raise
ActionNeeded
(
self
.
page
.
get_cgu
())
self
.
homep
.
stay_or_go
()
# Redirection not interpreted by browser. Manually redirect on homep
if
not
self
.
homep
.
is_here
():
raise
BrowserIncorrectPassword
()
@
need_login
def
iter_subscription_list
(
self
):
self
.
logger
.
debug
(
'call Browser.iter_subscription_list'
)
self
.
accountp
.
stay_or_go
()
return
self
.
page
.
iter_subscription_list
()
@
need_login
def
get_subscription
(
self
,
id
):
self
.
logger
.
debug
(
'call Browser.get_subscription'
)
assert
isinstance
(
id
,
basestring
)
for
sub
in
self
.
iter_subscription_list
():
if
id
==
sub
.
_id
:
return
sub
return
None
@
need_login
def
iter_history
(
self
,
sub
):
transactions
=
[]
self
.
logger
.
debug
(
'call Browser.iter_history'
)
self
.
paymentsp
.
stay_or_go
()
payments_url
=
self
.
page
.
get_last_payments_url
()
self
.
location
(
payments_url
)
assert
self
.
lastpaymentsp
.
is_here
()
urls
=
self
.
page
.
iter_last_payments
()
for
url
in
urls
:
self
.
location
(
url
)
assert
self
.
paymentdetailsp
.
is_here
()
for
payment
in
self
.
page
.
iter_payment_details
(
sub
):
transactions
.
append
(
payment
)
# go to a page with a "deconnexion" link so that logged property
# stays True and next call to do_login doesn't crash when using the
# blackbox
self
.
accountp
.
go
()
return
transactions
@
need_login
def
iter_documents
(
self
,
sub
):
self
.
logger
.
debug
(
'call Browser.iter_documents'
)
self
.
paymentsp
.
stay_or_go
()
payments_url
=
self
.
page
.
get_last_payments_url
()
self
.
location
(
payments_url
)
assert
self
.
lastpaymentsp
.
is_here
()
for
document
in
self
.
page
.
iter_documents
(
sub
):
yield
document
@
need_login
def
get_document
(
self
,
id
):
self
.
logger
.
debug
(
'call Browser.get_document'
)
assert
isinstance
(
id
,
basestring
)
subs
=
self
.
iter_subscription_list
()
for
sub
in
subs
:
for
b
in
self
.
iter_documents
(
sub
):
if
id
==
b
.
id
:
return
b
return
False
modules/ameli/module.py
deleted
100644 → 0
View file @
cfd368e8
# -*- coding: utf-8 -*-
# Copyright(C) 2013-2015 Christophe Lampin
#
# This file is part of a weboob module.
#
# This weboob module is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This weboob module is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from
__future__
import
unicode_literals
from
weboob.capabilities.bill
import
DocumentTypes
,
CapDocument
,
SubscriptionNotFound
,
DocumentNotFound
,
Subscription
,
Bill
from
weboob.tools.backend
import
Module
,
BackendConfig
from
weboob.tools.value
import
ValueBackendPassword
from
.browser
import
AmeliBrowser
__all__
=
[
'AmeliModule'
]
class
AmeliModule
(
Module
,
CapDocument
):
NAME
=
'ameli'
DESCRIPTION
=
'Ameli website: French Health Insurance'
MAINTAINER
=
'Christophe Lampin'
EMAIL
=
'weboob@lampin.net'
VERSION
=
'1.6'
LICENSE
=
'AGPLv3+'
BROWSER
=
AmeliBrowser
CONFIG
=
BackendConfig
(
ValueBackendPassword
(
'login'
,
label
=
'Numero de SS'
,
regexp
=
r'^\d{13}$'
,
masked
=
False
),
ValueBackendPassword
(
'password'
,
label
=
'Password'
,
masked
=
True
))
accepted_document_types
=
(
DocumentTypes
.
BILL
,)
def
create_default_browser
(
self
):
return
self
.
create_browser
(
self
.
config
[
'login'
]
.
get
(),
self
.
config
[
'password'
]
.
get
())
def
iter_subscription
(
self
):
return
self
.
browser
.
iter_subscription_list
()
def
get_subscription
(
self
,
_id
):
subscription
=
self
.
browser
.
get_subscription
(
_id
)
if
not
subscription
:
raise
SubscriptionNotFound
()
else
:
return
subscription
def
iter_documents_history
(
self
,
subscription
):
if
not
isinstance
(
subscription
,
Subscription
):
subscription
=
self
.
get_subscription
(
subscription
)
return
self
.
browser
.
iter_history
(
subscription
)
def
iter_documents
(
self
,
subscription
):
if
not
isinstance
(
subscription
,
Subscription
):
subscription
=
self
.
get_subscription
(
subscription
)
return
self
.
browser
.
iter_documents
(
subscription
)
def
get_document
(
self
,
id
):
bill
=
self
.
browser
.
get_document
(
id
)
if
not
bill
:
raise
DocumentNotFound
()
else
:
return
bill
def
download_document
(
self
,
bill
):
if
not
isinstance
(
bill
,
Bill
):
bill
=
self
.
get_document
(
bill
)
response
=
self
.
browser
.
open
(
bill
.
url
,
stream
=
True
)
if
not
response
or
response
.
headers
[
'content-type'
]
!=
"application/pdf"
:
return
None
return
response
.
content
modules/ameli/pages.py
deleted
100644 → 0
View file @
cfd368e8
# -*- coding: utf-8 -*-
# Copyright(C) 2013-2015 Christophe Lampin
#
# This file is part of a weboob module.
#
# This weboob module is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This weboob module is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from
__future__
import
unicode_literals
from
datetime
import
datetime
import
re
from
decimal
import
Decimal
from
weboob.browser.filters.html
import
Attr
,
XPathNotFound
from
weboob.browser.pages
import
HTMLPage
,
RawPage
,
LoggedPage
from
weboob.capabilities.bill
import
DocumentTypes
,
Subscription
,
Detail
,
Bill
from
weboob.browser.filters.standard
import
CleanText
,
Regexp
from
weboob.exceptions
import
BrowserUnavailable
# Ugly array to avoid the use of french locale
FRENCH_MONTHS
=
[
'janvier'
,
'février'
,
'mars'
,
'avril'
,
'mai'
,
'juin'
,
'juillet'
,
'août'
,
'septembre'
,
'octobre'
,
'novembre'
,
'décembre'
]
class
AmeliBasePage
(
HTMLPage
):
@
property
def
logged
(
self
):
if
self
.
doc
.
xpath
(
'//a[contains(text(), "Déconnexion")]'
):
logged
=
True
else
:
logged
=
False
self
.
logger
.
debug
(
'logged:
%
s'
%
(
logged
))
return
logged
def
is_error
(
self
):
errors
=
self
.
doc
.
xpath
(
'//*[@id="r_errors"]'
)
if
errors
:
return
errors
[
0
]
.
text_content
()
errors
=
CleanText
(
'//p[@class="msg_erreur"]'
,
default
=
''
)(
self
.
doc
)
if
errors
:
return
errors
errors
=
CleanText
(
'//div[@class="zone-alerte"]/span'
)(
self
.
doc
)
if
errors
:
return
errors
return
False
class
LoginPage
(
AmeliBasePage
):
def
login
(
self
,
login
,
password
):
form
=
self
.
get_form
(
'//form[@name="connexionCompteForm"]'
)
form
[
'connexioncompte_2numSecuriteSociale'
]
=
login
.
encode
(
'utf8'
)
form
[
'connexioncompte_2codeConfidentiel'
]
=
password
.
encode
(
'utf8'
)
form
.
submit
()
def
locate_to_cgu_page
(
self
):
try
:
# they've put a head tag inside body, yes i know...
url
=
Regexp
(
Attr
(
'//div[@id="connexioncompte_2"]//meta'
,
'content'
),
r'url=(.*)'
)(
self
.
doc
)
except
XPathNotFound
:
# no cgu to validate
return
self
.
browser
.
location
(
url
)
class
CguPage
(
AmeliBasePage
):
def
get_cgu
(
self
):
return
CleanText
(
'//div[@class="page_nouvelles_cgus"]/p[1]'
)(
self
.
doc
)
class
HomePage
(
AmeliBasePage
):
pass
class
AccountPage
(
AmeliBasePage
):
def
iter_subscription_list
(
self
):
names_list
=
self
.
doc
.
xpath
(
'//span[@class="NomEtPrenomLabel"]'
)
fullname
=
CleanText
(
newlines
=
True
)
.
filter
(
names_list
[
0
])
number
=
re
.
sub
(
r'[^\d]+'
,
''
,
CleanText
(
'//span[@class="blocNumSecu"]'
,
replace
=
[(
' '
,
''
)])(
self
.
doc
))
sub
=
Subscription
(
number
)
sub
.
_id
=
number
sub
.
label
=
fullname
firstname
=
CleanText
(
'//span[@class="prenom-titulaire"]'
)(
self
.
doc
)
sub
.
subscriber
=
firstname
yield
sub
class
PaymentsPage
(
AmeliBasePage
):
def
get_last_payments_url
(
self
):
begin_date
=
self
.
doc
.
xpath
(
'//input[@id="paiements_1dateDebut"]/@data-mindate'
)[
0
]
end_date
=
self
.
doc
.
xpath
(
'//input[@id="paiements_1dateFin"]/@data-maxdate'
)[
0
]
url
=
(
'/PortailAS/paiements.do?actionEvt=afficherPaiementsComplementaires&DateDebut='
+
begin_date
+
'&DateFin='
+
end_date
+
'&Beneficiaire=tout_selectionner&afficherReleves=false&afficherIJ=false&afficherInva=false'
'&afficherRentes=false&afficherRS=false&indexPaiement=&idNotif='
)
return
url
class
LastPaymentsPage
(
LoggedPage
,
AmeliBasePage
):
def
iter_last_payments
(
self
):
elts
=
self
.
doc
.
xpath
(
'//li[@class="rowitem remboursement"]'
)
for
elt
in
elts
:
items
=
Regexp
(
CleanText
(
'./@onclick'
),
r".*ajaxCallRemoteChargerDetailPaiement \('(\w+={0,2})', '(\w+)', '(\d+)', '(\d+)'\).*"
,
'
\\
1,
\\
2,
\\
3,
\\
4'
)(
elt
)
.
split
(
','
)
yield
"/PortailAS/paiements.do?actionEvt=chargerDetailPaiements&idPaiement="
+
items
[
0
]
+
"&naturePaiement="
+
items
[
1
]
+
"&indexGroupe="
+
items
[
2
]
+
"&indexPaiement="
+
items
[
3
]
def
iter_documents
(
self
,
sub
):
elts
=
self
.
doc
.
xpath
(
'//li[@class="rowdate"]'
)
for
elt
in
elts
:
try
:
elt
.
xpath
(
'.//a[contains(@id,"lienPDFReleve")]'
)[
0
]
except
IndexError
:
continue
date_str
=
elt
.
xpath
(
'.//span[contains(@id,"moisEnCours")]'
)[
0
]
.
text
month_str
=
date_str
.
split
()[
0
]
date
=
datetime
.
strptime
(
re
.
sub
(
month_str
,
str
(
FRENCH_MONTHS
.
index
(
month_str
)
+
1
),
date_str
),
"
%
m
%
Y"
)
.
date
()
bil
=
Bill
()
bil
.
id
=
sub
.
_id
+
"."
+
date
.
strftime
(
"
%
Y
%
m"
)
bil
.
date
=
date
bil
.
format
=
'pdf'
bil
.
type
=
DocumentTypes
.
BILL
bil
.
label
=
date
.
strftime
(
"
%
Y
%
m
%
d"
)
bil
.
url
=
'/PortailAS/PDFServletReleveMensuel.dopdf?PDF.moisRecherche='
+
date
.
strftime
(
"
%
m
%
Y"
)
yield
bil
def
get_document
(
self
,
bill
):
self
.
location
(
bill
.
url
,
params
=
bill
.
_args
)
class
PaymentDetailsPage
(
AmeliBasePage
):
def
iter_payment_details
(
self
,
sub
):
id_str
=
self
.
doc
.
xpath
(
'//div[@class="entete container"]/h2'
)[
0
]
.
text
.
strip
()
m
=
re
.
match
(
r'.*le (.*) pour un montant de.*'
,
id_str
)
if
m
:
blocs_benes
=
self
.
doc
.
xpath
(
'//span[contains(@id,"nomBeneficiaire")]'
)
blocs_prestas
=
self
.
doc
.
xpath
(
'//table[@id="tableauPrestation"]'
)
i
=
0
last_bloc
=
len
(
blocs_benes
)
for
i
in
range
(
0
,
last_bloc
):
bene
=
blocs_benes
[
i
]
.
text
id_str
=
m
.
group
(
1
)
id_date
=
datetime
.
strptime
(
id_str
,
'
%
d/
%
m/
%
Y'
)
.
date
()
id
=
sub
.
_id
+
"."
+
datetime
.
strftime
(
id_date
,
"
%
Y
%
m
%
d"
)
table
=
blocs_prestas
[
i
]
.
xpath
(
'.//tr'
)
line
=
1
last_date
=
None
for
tr
in
table
:
tds
=
tr
.
xpath
(
'.//td'
)
if
len
(
tds
)
==
0
:
continue
det
=
Detail
()
# TO TEST : Indemnités journalières : Pas pu tester de cas de figure similaire dans la nouvelle mouture du site
if
len
(
tds
)
==
4
:
date_str
=
Regexp
(
pattern
=
r'.*<br/>(\d+/\d+/\d+)\).*'
)
.
filter
(
tds
[
0
]
.
text
)
det
.
id
=
id
+
"."
+
str
(
line
)
det
.
label
=
tds
[
0
]
.
xpath
(
'.//span'
)[
0
]
.
text
.
strip
()
jours
=
tds
[
1
]
.
text
if
jours
is
None
:
jours
=
'0'
montant
=
tds
[
2
]
.
text
if
montant
is
None
:
montant
=
'0'
price
=
tds
[
3
]
.
text
if
price
is
None
:
price
=
'0'
if
date_str
is
None
or
date_str
==
''
:
det
.
infos
=
''
det
.
datetime
=
last_date
else
:
det
.
infos
=
date_str
+
' ('
+
re
.
sub
(
r'[^\d,-]+'
,
''
,
jours
)
+
'j) * '
+
re
.
sub
(
r'[^\d,-]+'
,
''
,
montant
)
+
'€'
det
.
datetime
=
datetime
.
strptime
(
date_str
.
split
(
' '
)[
3
],
'
%
d/
%
m/
%
Y'
)
.
date
()
last_date
=
det
.
datetime
det
.
price
=
Decimal
(
re
.
sub
(
r'[^\d,-]+'
,
''
,
price
)
.
replace
(
','
,
'.'
))
if
len
(
tds
)
==
5
:
date_str
=
Regexp
(
pattern
=
r'\w*(\d{2})/(\d{2})/(\d{4}).*'
,
template
=
'
\\
1/
\\
2/
\\
3'
,
default
=
""
)
.
filter
(
""
.
join
(
tds
[
0
]
.
itertext
()))
det
.
id
=
id
+
"."
+
str
(
line
)
det
.
label
=
bene
+
' - '
+
tds
[
0
]
.
xpath
(
'.//span'
)[
0
]
.
text
.
strip
()
paye
=
tds
[
1
]
.
text
if
paye
is
None
:
paye
=
'0'
base
=
tds
[
2
]
.
text
if
base
is
None
:
base
=
'0'
tdtaux
=
tds
[
3
]
.
xpath
(
'.//span'
)[
0
]
.
text
if
tdtaux
is
None
:
taux
=
'0'
else
:
taux
=
tdtaux
.
strip
()
tdprice
=
tds
[
4
]
.
xpath
(
'.//span'
)[
0
]
.
text
if
tdprice
is
None
:
price
=
'0'
else
:
price
=
tdprice
.
strip
()
if
date_str
is
None
or
date_str
==
''
:
det
.
infos
=
''
det
.
datetime
=
last_date
else
:
det
.
infos
=
' Payé '
+
re
.
sub
(
r'[^\d,-]+'
,
''
,
paye
)
+
'€ / Base '
+
re
.
sub
(
r'[^\d,-]+'
,
''
,
base
)
+
'€ / Taux '
+
re
.
sub
(
r'[^\d,-]+'
,
''
,
taux
)
+
'
%
'
det
.
datetime
=
datetime
.
strptime
(
date_str
,
'
%
d/
%
m/
%
Y'
)
.
date
()
last_date
=
det
.
datetime
det
.
price
=
Decimal
(
re
.
sub
(
r'[^\d,-]+'
,
''
,
price
)
.
replace
(
','
,
'.'
))
line
=
line
+
1
yield
det
class
Raw
(
LoggedPage
,
RawPage
):
pass
class
UnavailablePage
(
HTMLPage
):
def
on_load
(
self
):
raise
BrowserUnavailable
(
CleanText
(
'//span[@class="texte-indispo"]'
)(
self
.
doc
))
modules/ameli/test.py
deleted
100644 → 0
View file @
cfd368e8
# -*- coding: utf-8 -*-
# Copyright(C) 2013-2015 Christophe Lampin
#
# This file is part of a weboob module.
#
# This weboob module is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This weboob module is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this weboob module. If not, see <http://www.gnu.org/licenses/>.
from
weboob.tools.test
import
BackendTest
class
AmeliTest
(
BackendTest
):
MODULE
=
'ameli'
def
test_ameli
(
self
):
for
subscription
in
self
.
backend
.
iter_subscription
():
list
(
self
.
backend
.
iter_documents_history
(
subscription
.
id
))
for
bill
in
self
.
backend
.
iter_documents
(
subscription
.
id
):
self
.
backend
.
download_document
(
bill
.
id
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment