305 lines
12 KiB
Python
305 lines
12 KiB
Python
# barbacompta - invoicing for dummies
|
|
# Copyright (C) 2019-2021 Entr'ouvert
|
|
#
|
|
# This program 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 program 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 program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import io
|
|
import xml.etree.ElementTree as ET
|
|
|
|
import facturx
|
|
import pytest
|
|
from django.contrib.auth import get_user_model
|
|
from weasyprint import HTML
|
|
|
|
from eo_gestion.eo_facture.models import Facture
|
|
|
|
|
|
def test_limitations(db):
|
|
facture = Facture.objects.get(
|
|
client__nom='c3f42bb0d75d379', contrat__intitule='1da9dc528d5c7191bc87c7', ordre=137
|
|
)
|
|
User = get_user_model()
|
|
creator = User.objects.get(username='admin')
|
|
facture_avoir = facture.cancel(creator)
|
|
|
|
with pytest.raises(AssertionError, match='cannot cancel a canceled invoice'):
|
|
facture_avoir.cancel(creator)
|
|
with pytest.raises(AssertionError, match='cannot cancel twice an invoice'):
|
|
facture.cancel(creator)
|
|
|
|
|
|
def test_facture_avoir(app):
|
|
response = app.get("/eo_facture/facture/").follow()
|
|
|
|
response.form.set("username", "admin")
|
|
response.form.set("password", "admin")
|
|
|
|
homepage = response.form.submit().follow()
|
|
factures_page = homepage.click("Factures")
|
|
|
|
facture_0137_page = factures_page.click('F20190137')
|
|
assert 'F20190137' in facture_0137_page.html.find('div', {'class': 'breadcrumbs'}).text
|
|
assert facture_0137_page.html.find('input', {'id': 'id_intitule'})['value'] == '95e0eb4429394cd2'
|
|
assert len([x for x in Facture.objects.all() if x.code() == 'F20190137']) == 1
|
|
facture = [x for x in Facture.objects.all() if x.code() == 'F20190137'][0]
|
|
assert facture.factures_avoir.count() == 0
|
|
assert 'Annuler' in [li.a.text for li in facture_0137_page.html.find_all('li')]
|
|
facture_avoir_page = facture_0137_page.click("Annuler")
|
|
facture_avoir_page = facture_avoir_page.follow()
|
|
|
|
# can't cancel invoice twice
|
|
facture_0137_page = factures_page.click('F20190137')
|
|
assert 'Annuler' not in [li.a.text for li in facture_0137_page.html.find_all('li')]
|
|
|
|
assert (
|
|
'Facture proforma du 2019-10-09' in facture_avoir_page.html.find('div', {'class': 'breadcrumbs'}).text
|
|
)
|
|
assert (
|
|
facture_avoir_page.html.find('input', {'id': 'id_intitule'})['value']
|
|
== 'AVOIR POUR LA FACTURE 95e0eb4429394cd2'
|
|
)
|
|
# can't cancel a canceled invoice
|
|
assert 'Annuler' not in [li.a.text for li in facture_avoir_page.html.find_all('li')]
|
|
assert facture.factures_avoir.count() == 1
|
|
facture_avoir = facture.factures_avoir.first()
|
|
assert facture_avoir.code() == 'Facture proforma du 2019-10-09'
|
|
assert facture_avoir.annulation == facture
|
|
assert facture_avoir.proforma is True
|
|
assert facture.intitule == '95e0eb4429394cd2'
|
|
assert facture_avoir.intitule == 'AVOIR POUR LA FACTURE 95e0eb4429394cd2'
|
|
assert str(facture.lignes.first()) == '503aa5f7b148f6a106eda9274d5130c1 pour 800.85 €'
|
|
assert str(facture_avoir.lignes.first()) == '503aa5f7b148f6a106eda9274d5130c1 pour -800.85 €'
|
|
|
|
facture_avoir_page.form.fields['proforma'][0].checked = False
|
|
facture_avoir_page = facture_avoir_page.form.submit('_continue')
|
|
facture_avoir_page = facture_avoir_page.follow()
|
|
assert 'F20190237' in facture_avoir_page.html.find('div', {'class': 'breadcrumbs'}).text
|
|
assert (
|
|
facture_avoir_page.html.find('input', {'id': 'id_intitule'})['value']
|
|
== 'AVOIR POUR LA FACTURE 95e0eb4429394cd2'
|
|
)
|
|
facture_avoir = Facture.objects.get(id=facture_avoir.id) # object was updated
|
|
assert facture_avoir.code() == 'F20190237'
|
|
|
|
assert 'Imprimer' in [li.a.text for li in facture_avoir_page.html.find_all('li')]
|
|
factur_x_page = app.get("/eo_facture/facture/%s/view_pdf/F20190137.pdf?facturx" % facture_avoir.id)
|
|
factur_x_pdf = factur_x_page.body
|
|
_, factur_x_xml = facturx.get_facturx_xml_from_pdf(io.BytesIO(factur_x_pdf))
|
|
root = ET.fromstring(factur_x_xml)
|
|
|
|
def helper(root):
|
|
tag = root.tag.split('}')[-1]
|
|
if len(root) == 0:
|
|
return [tag, root.text or '']
|
|
else:
|
|
return [tag] + [helper(node) for node in root]
|
|
|
|
# factur-x facture avoir
|
|
assert helper(root) == [
|
|
'CrossIndustryInvoice',
|
|
[
|
|
'ExchangedDocumentContext',
|
|
['BusinessProcessSpecifiedDocumentContextParameter', ['ID', 'A1']],
|
|
['GuidelineSpecifiedDocumentContextParameter', ['ID', 'urn:factur-x.eu:1p0:basicwl']],
|
|
],
|
|
[
|
|
'ExchangedDocument',
|
|
['ID', 'F20190237'],
|
|
['TypeCode', '381'],
|
|
['IssueDateTime', ['DateTimeString', '20190101']],
|
|
],
|
|
[
|
|
'SupplyChainTradeTransaction',
|
|
[
|
|
'ApplicableHeaderTradeAgreement',
|
|
[
|
|
'SellerTradeParty',
|
|
['Name', "Entr'ouvert"],
|
|
['SpecifiedLegalOrganization', ['ID', '44317013900036']],
|
|
['PostalTradeAddress', ['CountryID', 'FR']],
|
|
['SpecifiedTaxRegistration', ['ID', 'FR09491081899']],
|
|
],
|
|
[
|
|
'BuyerTradeParty',
|
|
['Name', 'c3f42bb0d75d379'],
|
|
[
|
|
'SpecifiedLegalOrganization',
|
|
['ID', ''],
|
|
],
|
|
],
|
|
],
|
|
['ApplicableHeaderTradeDelivery', ''],
|
|
[
|
|
'ApplicableHeaderTradeSettlement',
|
|
['InvoiceCurrencyCode', 'EUR'],
|
|
['SpecifiedTradeSettlementPaymentMeans', ['TypeCode', '97']],
|
|
[
|
|
'ApplicableTradeTax',
|
|
['CalculatedAmount', '-160.17'],
|
|
['TypeCode', 'VAT'],
|
|
['BasisAmount', '-800.85'],
|
|
['CategoryCode', 'S'],
|
|
['RateApplicablePercent', '20.00'],
|
|
],
|
|
[
|
|
'SpecifiedTradeSettlementHeaderMonetarySummation',
|
|
['LineTotalAmount', '-800.85'],
|
|
['TaxBasisTotalAmount', '-800.85'],
|
|
['TaxTotalAmount', '-160.17'],
|
|
['GrandTotalAmount', '-961.02'],
|
|
['DuePayableAmount', '-961.02'],
|
|
],
|
|
['InvoiceReferencedDocument', ['IssuerAssignedID', 'F20190137']],
|
|
],
|
|
],
|
|
]
|
|
|
|
|
|
def test_facture_pdf(app):
|
|
facture = Facture.objects.get(
|
|
client__nom='c3f42bb0d75d379', contrat__intitule='1da9dc528d5c7191bc87c7', ordre=137
|
|
)
|
|
|
|
def helper(root):
|
|
tag = root.tag.split('}')[-1]
|
|
if len(root) == 0:
|
|
return [tag, root.text or '']
|
|
else:
|
|
return [tag] + [helper(node) for node in root]
|
|
|
|
# facture F20190137
|
|
facture_html = HTML(string=facture.html())
|
|
root = facture_html.etree_element
|
|
facture_content = helper(root)
|
|
assert facture_content == [
|
|
'html',
|
|
['head', ['meta', ''], ['title', 'Facture F20190137 - 95e0eb4429394cd2'], ['meta', ''], ['link', '']],
|
|
[
|
|
'body',
|
|
[
|
|
'div',
|
|
[
|
|
'table',
|
|
[
|
|
'tbody',
|
|
['tr', ['td', 'Facture'], ['td', 'F20190137']],
|
|
['tr', ['td', 'Date'], ['td', '09/10/2019']],
|
|
['tr', ['td', 'Échéance'], ['td', '23/11/2019']],
|
|
],
|
|
],
|
|
],
|
|
[
|
|
'div',
|
|
['img', ''],
|
|
[
|
|
'div',
|
|
'\nSociété Coopérative et Participative\n'
|
|
'SARL au capital variable de 15200 €\n'
|
|
'169 rue du château\n'
|
|
'75014 PARIS\n'
|
|
'FRANCE\n\n'
|
|
'Tél : 01 43 35 01 35\n'
|
|
'Email : gerant@entrouvert.com\n'
|
|
'Web : https://www.entrouvert.com\n'
|
|
'RCS : Paris\n'
|
|
'NAF/APE : 6201Z\n'
|
|
'SIRET : 443 170 139 00036\n'
|
|
'Numéro TVA : FR 08443170139',
|
|
],
|
|
],
|
|
['div', ['strong', 'c3f42bb0d75d379'], ['br', '']],
|
|
[
|
|
'div',
|
|
['div', ['h1', '95e0eb4429394cd2']],
|
|
['div', ['p', 'e8078180ca3a563dd45070362a6a102e']],
|
|
[
|
|
'table',
|
|
[
|
|
'thead',
|
|
[
|
|
'tr',
|
|
['td', 'Description'],
|
|
['td', 'Quantité'],
|
|
['td', 'Prix unitaire'],
|
|
['td', 'Prix HT'],
|
|
],
|
|
],
|
|
[
|
|
'tbody',
|
|
[
|
|
'tr',
|
|
['td', '503aa5f7b148f6a106eda9274d5130c1'],
|
|
['td', '1,00'],
|
|
['td', '800,85'],
|
|
['td', '800,85'],
|
|
],
|
|
],
|
|
[
|
|
'tfoot',
|
|
['tr', ['td', 'Sous-total :'], ['td', '800,85']],
|
|
['tr', ['td', 'Total TVA (à 20,00 %) :'], ['td', '160,17']],
|
|
['tr', ['td', 'Total TTC :'], ['td', '961,02']],
|
|
],
|
|
],
|
|
['p', "Paiement total de 961,02 € à verser au nom d'Entr'ouvert."],
|
|
['p', 'Mode de paiement : virement'],
|
|
],
|
|
[
|
|
'div',
|
|
[
|
|
'div',
|
|
'Domiciliation bancaire : BP RIVES MAINE\n'
|
|
'BIC : CCBPFRPPMTG\n'
|
|
'IBAN : FR76 1020 7000 9104 0910 0252 059\n'
|
|
'Code étab. : 10207 Code guichet : 00091 Numéro de compte : 04091002520 Clé RIB : 59',
|
|
],
|
|
['div', ['img', '']],
|
|
['div', ['a', 'https://www.entrouvert.com/fr/conditions-generales-de-vente/']],
|
|
],
|
|
],
|
|
]
|
|
|
|
# facture avoir
|
|
User = get_user_model()
|
|
creator = User.objects.get(username='admin')
|
|
facture_avoir = facture.cancel(creator)
|
|
facture_avoir.proforma = False
|
|
facture_avoir.save()
|
|
facture_avoir_html = HTML(string=facture_avoir.html())
|
|
root_avoir = facture_avoir_html.etree_element
|
|
facture_avoir_content = helper(root_avoir)
|
|
assert facture_avoir_content[2][1][1][1] == [
|
|
'tbody',
|
|
['tr', ['td', 'Facture'], ['td', 'F20190237']],
|
|
['tr', ['td', 'Annule'], ['td', 'F20190137']],
|
|
['tr', ['td', 'Date'], ['td', '09/10/2019']],
|
|
['tr', ['td', 'Échéance'], ['td', '23/11/2019']],
|
|
]
|
|
|
|
# facture avoir proformat
|
|
facture.proforma = True
|
|
facture.save()
|
|
facture_avoir.proforma = True
|
|
facture_avoir.save()
|
|
facture_avoir_html = HTML(string=facture_avoir.html())
|
|
root_avoir = facture_avoir_html.etree_element
|
|
facture_avoir_content = helper(root_avoir)
|
|
assert facture_avoir_content[2][1][1][1] == [
|
|
'tbody',
|
|
['tr', ['td', 'Facture'], ['td', 'proforma']],
|
|
['tr', ['td', 'Annule'], ['td', 'proforma']],
|
|
['tr', ['td', 'Date'], ['td', '09/10/2019']],
|
|
['tr', ['td', 'Échéance'], ['td', '23/11/2019']],
|
|
]
|