Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement schedule task to collect billing invoice #417

Merged
Merged
Show file tree
Hide file tree
Changes from 55 commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
33c32e1
Implement schedule task to collect billing invoice
Oct 29, 2024
fb65f1f
consider sourcery-ai and fix isort
Oct 29, 2024
75a08f6
Implement billing settings form for organizer
odkhang Oct 31, 2024
b0d7932
Resolve conflict
odkhang Oct 31, 2024
9b499db
Fix flake8 in pipeline
odkhang Oct 31, 2024
a17173a
implement create and save payment_information
odkhang Oct 21, 2024
055b2d7
Fix isort, flake8 in pipeline
odkhang Oct 31, 2024
b7dbc11
Merge branch 'development' into feat-383-implement-schedule-task
Oct 31, 2024
045bfe4
implement payment-information-v1
odkhang Oct 24, 2024
e7510d2
src/pretix/control/views/organizer_views/organizer_view.py
odkhang Oct 31, 2024
0c4b5f6
Code refactoring
odkhang Oct 28, 2024
f596f95
Code refactoring
odkhang Oct 28, 2024
ede87ff
Remove payment_information attribute
odkhang Oct 28, 2024
eb22165
Implement tax validation
odkhang Oct 30, 2024
8cf14e5
Update code
odkhang Oct 30, 2024
25f70a9
Update code
odkhang Oct 30, 2024
abc5731
Fix flake8 in pipeline
odkhang Oct 30, 2024
dbf24b2
Add pyvat package
odkhang Oct 30, 2024
7b1fe49
Fix flake8 in pipeline
odkhang Oct 31, 2024
27b71a6
Latest code
odkhang Oct 31, 2024
063d2a6
Fix flake8 in pipeline
odkhang Oct 31, 2024
c4b9ce5
Add logger error
odkhang Oct 31, 2024
7133019
Fix flake8 in pipeline
odkhang Oct 31, 2024
891d3e8
implement trigger invoice to organizer
Nov 1, 2024
723ce29
Merge remote-tracking branch 'upstream/feat-383-implement-schedule-ta…
odkhang Nov 1, 2024
9a5929c
Merge branch 'development' into feature-380
odkhang Nov 1, 2024
35f3376
Merge branch 'development' into feature-380
odkhang Nov 1, 2024
3d4249a
Fix conflict pretix/base/migration
odkhang Nov 1, 2024
072ef08
Merge branch 'feature-380' of github.com:odkhang/eventyay-tickets int…
odkhang Nov 1, 2024
edd7d64
Implement auto billing charge
odkhang Nov 1, 2024
97fbc9c
Merge branch 'development' into feature-380
odkhang Nov 1, 2024
8b5f87e
Fix conflict pretix base migration
odkhang Nov 1, 2024
17064b1
Update pretix base migration
odkhang Nov 1, 2024
db4f7b2
Add logging information and modify error logging
odkhang Nov 4, 2024
fa91edd
Merge branch 'feature-380' into feature-380-v3
odkhang Nov 4, 2024
d7d0ea9
Merge remote-tracking branch 'upstream/feature-380' into feat-383-imp…
Nov 4, 2024
3fb85e2
Implement auto billing charge
odkhang Nov 4, 2024
022a8fb
update schedule task
odkhang Nov 5, 2024
5fdbe3d
Merge branch 'development' into feat-383-implement-schedule-task
odkhang Nov 5, 2024
c2c8524
fix isort, flake8 and update branch
odkhang Nov 5, 2024
c6f5fdd
fix isort pipeline
lcduong Nov 5, 2024
582f94b
Merge remote-tracking branch 'upstream/feature-380-v3' into feat-383-…
lcduong Nov 5, 2024
f70b561
Implement automatic payment charging
odkhang Nov 6, 2024
1d20c45
update invoice template
lcduong Nov 6, 2024
2b932f5
Merge remote-tracking branch 'upstream/feature-380-v3' into feat-383-…
lcduong Nov 6, 2024
b0faf51
change var name, format code
lcduong Nov 6, 2024
bd8dfc4
Implement automatic payment charging
odkhang Nov 6, 2024
9922347
Merge remote-tracking branch 'upstream/feature-383' into feat-383-imp…
lcduong Nov 6, 2024
3a1118b
Add comment
odkhang Nov 6, 2024
26e10d4
Merge remote-tracking branch 'upstream/feature-383' into feat-383-imp…
lcduong Nov 6, 2024
7605c38
Implement show error message
odkhang Nov 6, 2024
7abf71c
Merge remote-tracking branch 'upstream/feature-383' into feat-383-imp…
lcduong Nov 6, 2024
e19de5c
handle case update invoice to expired
lcduong Nov 6, 2024
67be7eb
fix isort, flake8 pipeline
lcduong Nov 6, 2024
ffcf3f5
Update api to trigger billing task for testing
lcduong Nov 6, 2024
3b19d22
Implement stripe webhook secret key in global setting
odkhang Nov 7, 2024
5d5e638
Add comment,save tax_id, show error and sucess message
odkhang Nov 7, 2024
9afbad4
Merge remote-tracking branch 'upstream/feature-383' into feat-383-imp…
lcduong Nov 8, 2024
8f48821
Merge remote-tracking branch 'upstream/feature-380' into feat-383-imp…
lcduong Nov 8, 2024
e555acd
handle case get mail backend for global settings
lcduong Nov 8, 2024
0673121
fix isort pipeline
lcduong Nov 8, 2024
4eee5a7
fix sending custom mail smtp
lcduong Nov 8, 2024
62f7bf7
tickets fee should be calculated based on net amount
lcduong Nov 8, 2024
b04b50f
remove unsed import to fix pipeline
lcduong Nov 8, 2024
2e7cfd3
correct import to fix isort
lcduong Nov 8, 2024
13b2944
Update code
odkhang Nov 13, 2024
38154c7
Fix flake8 in pipeline
odkhang Nov 13, 2024
ea52541
Merge branch 'development' into feature-380
odkhang Nov 13, 2024
8512f8c
Update code
odkhang Nov 14, 2024
4aa0e3c
Update code
odkhang Nov 14, 2024
e41650b
Update code
odkhang Nov 14, 2024
aa86746
fix flake8 in pipeline
odkhang Nov 14, 2024
f76094a
move validation to clean method and move get_country_name to countrie…
odkhang Nov 15, 2024
ddde2c6
Merge remote-tracking branch 'upstream/feature-380' into feat-383-imp…
lcduong Nov 15, 2024
80e1fe1
Merge branch 'development' into feat-383-implement-schedule-task
lcduong Nov 18, 2024
5ed3a49
formar code
lcduong Nov 18, 2024
14a7b09
Merge branch 'development' into feat-383-implement-schedule-task
odkhang Nov 18, 2024
4cc2bb8
Merge branch 'development' into feat-383-implement-schedule-task
lcduong Nov 19, 2024
fe0627b
merge migration file
lcduong Nov 19, 2024
1ef5f46
fix isort
lcduong Nov 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions deployment/docker/pretix.bash
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ if [ "$1" == "taskworker" ]; then
exec celery -A pretix.celery_app worker -l info "$@"
fi

if [ "$1" == "taskbeat" ]; then
shift
exec celery -A pretix.celery_app beat -l info "$@"
fi

if [ "$1" == "upgrade" ]; then
exec python3 -m pretix updatestyles
fi
Expand Down
10 changes: 10 additions & 0 deletions deployment/docker/supervisord/pretixbeat.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[program:pretixtask]
command=/usr/local/bin/pretix taskbeat
autostart=true
autorestart=true
priority=5
user=pretixuser
stdout_logfile=/dev/fd/1
stdout_logfile_maxbytes=0
stderr_logfile=/dev/fd/2
stderr_logfile_maxbytes=0
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ dependencies = [
'django-sso==3.0.2',
'PyJWT~=2.8.0',
'exhibitors @ git+https://github.com/fossasia/eventyay-tickets-exhibitors.git@master',
'pyvat==1.3.18',
]

[project.optional-dependencies]
Expand Down
2 changes: 2 additions & 0 deletions src/pretix/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from pretix.api.views import cart

from ..eventyay_common.views.billing import BillingInvoicePreview
from .views import (
checkin, device, event, exporters, item, oauth, order, organizer, upload,
user, version, voucher, waitinglist, webhooks,
Expand Down Expand Up @@ -98,4 +99,5 @@
url(r"^upload$", upload.UploadView.as_view(), name="upload"),
url(r"^me$", user.MeView.as_view(), name="user.me"),
url(r"^version$", version.VersionView.as_view(), name="version"),
url(r"^billing-testing/(?P<task>[^/]+)", BillingInvoicePreview.as_view(), name="billing-testing"),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Generated by Django 5.1.2 on 2024-10-31 09:30

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("pretixbase", "0003_event_is_video_creation_and_more"),
]

operations = [
migrations.CreateModel(
name="OrganizerBillingModel",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False
),
),
("primary_contact_name", models.CharField(max_length=255)),
("primary_contact_email", models.EmailField(max_length=255)),
("company_or_organization_name", models.CharField(max_length=255)),
("address_line_1", models.CharField(max_length=255)),
("address_line_2", models.CharField(max_length=255)),
("city", models.CharField(max_length=255)),
("zip_code", models.CharField(max_length=255)),
("country", models.CharField(max_length=255)),
("preferred_language", models.CharField(max_length=255)),
("tax_id", models.CharField(max_length=255)),
("stripe_customer_id", models.CharField(max_length=255, null=True)),
(
"stripe_payment_method_id",
models.CharField(max_length=255, null=True),
),
("stripe_setup_intent_id", models.CharField(max_length=255, null=True)),
(
"organizer",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="billing",
to="pretixbase.organizer",
),
),
],
),
]
49 changes: 49 additions & 0 deletions src/pretix/base/migrations/0003_create_billing_invoice_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Generated by Django 5.1.2 on 2024-10-29 04:33

import datetime

import django.contrib.postgres.fields
import django.db.models.deletion
from django.db import migrations, models

import pretix.base.models.base


class Migration(migrations.Migration):

dependencies = [
('pretixbase', '0003_alter_cachedcombinedticket_id_alter_cachedticket_id_and_more'),
]

operations = [
migrations.CreateModel(
name='BillingInvoice',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
('status', models.CharField(default='n', max_length=1)),
('amount', models.DecimalField(decimal_places=2, max_digits=10)),
('currency', models.CharField(max_length=3)),
('ticket_fee', models.DecimalField(decimal_places=2, max_digits=10)),
('payment_method', models.CharField(max_length=20, null=True)),
('paid_datetime', models.DateTimeField(blank=True, null=True)),
('note', models.TextField(null=True)),
('monthly_bill', models.DateField(default=datetime.date.today)),
('created_at', models.DateTimeField(auto_now_add=True)),
('created_by', models.CharField(max_length=50)),
('updated_at', models.DateTimeField(auto_now=True)),
('updated_by', models.CharField(max_length=50)),
('last_reminder_datetime', models.DateTimeField(blank=True, null=True)),
('next_reminder_datetime', models.DateTimeField(blank=True, null=True)),
('reminder_schedule', django.contrib.postgres.fields.ArrayField(base_field=models.IntegerField(), default=list, size=None)),
('reminder_enabled', models.BooleanField(default=True)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.event')),
('organizer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pretixbase.organizer')),
],
options={
'verbose_name': 'Billing Invoice',
'verbose_name_plural': 'Billing Invoices',
'ordering': ('-created_at',),
},
bases=(models.Model, pretix.base.models.base.LoggingMixin),
),
]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let introduce only 1 DB migration file, to not inflate the number.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I solved it, only 1 migration file now for new table creation.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2024-11-05 04:08

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("pretixbase", "0003_create_billing_invoice_table"),
]

operations = [
migrations.AddField(
model_name="billinginvoice",
name="stripe_payment_intent_id",
field=models.CharField(max_length=50, null=True),
)
]
1 change: 1 addition & 0 deletions src/pretix/base/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from ..settings import GlobalSettingsObject_SettingsStore
from .auth import U2FDevice, User, WebAuthnDevice
from .base import CachedFile, LoggedModel, cachedfile_name
from .billing import BillingInvoice
from .checkin import Checkin, CheckinList
from .devices import Device, Gate
from .event import (
Expand Down
59 changes: 59 additions & 0 deletions src/pretix/base/models/billing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from datetime import date

from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.translation import gettext_lazy as _
from django_scopes import ScopedManager

from pretix.base.models import LoggedModel


class BillingInvoice(LoggedModel):
STATUS_PENDING = "n"
STATUS_PAID = "p"
STATUS_EXPIRED = "e"
STATUS_CANCELED = "c"

STATUS_CHOICES = [
(STATUS_PENDING, _("pending")),
(STATUS_PAID, _("paid")),
(STATUS_EXPIRED, _("expired")),
(STATUS_CANCELED, _("canceled")),
]

organizer = models.ForeignKey('Organizer', on_delete=models.CASCADE)
# organizer_billing = models.ForeignKey('OrganizerBilling', on_delete=models.CASCADE)
event = models.ForeignKey('Event', on_delete=models.CASCADE)

status = models.CharField(max_length=1, choices=STATUS_CHOICES, default=STATUS_PENDING)
amount = models.DecimalField(max_digits=10, decimal_places=2)
currency = models.CharField(max_length=3)

ticket_fee = models.DecimalField(max_digits=10, decimal_places=2)
payment_method = models.CharField(max_length=20, null=True, blank=True)
paid_datetime = models.DateTimeField(null=True, blank=True)
note = models.TextField(null=True, blank=True)

monthly_bill = models.DateField(default=date.today)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.CharField(max_length=50)
updated_at = models.DateTimeField(auto_now=True)
updated_by = models.CharField(max_length=50)

last_reminder_datetime = models.DateTimeField(null=True, blank=True)
next_reminder_datetime = models.DateTimeField(null=True, blank=True)
reminder_schedule = ArrayField(
models.IntegerField(),
default=list, # Sets the default to an empty list
blank=True,
help_text="Days after creation for reminders, e.g., [14, 28]"
)
reminder_enabled = models.BooleanField(default=True)
stripe_payment_intent_id = models.CharField(max_length=50, null=True, blank=True)

objects = ScopedManager(organizer='organizer')

class Meta:
verbose_name = "Billing Invoice"
verbose_name_plural = "Billing Invoices"
ordering = ("-created_at",)
89 changes: 89 additions & 0 deletions src/pretix/base/models/organizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,92 @@ def get_events_with_permission(self, permission, request=None):
return self.get_events_with_any_permission()
else:
return self.team.organizer.events.none()


class OrganizerBillingModel(models.Model):
"""
Billing model - support billing information for organizer
"""

organizer = models.ForeignKey(
"Organizer", on_delete=models.CASCADE, related_name="billing"
)

primary_contact_name = models.CharField(
max_length=255,
verbose_name=_("Primary Contact Name"),
)

primary_contact_email = models.EmailField(
max_length=255,
verbose_name=_("Primary Contact Email"),
)

company_or_organization_name = models.CharField(
max_length=255,
verbose_name=_("Company or Organization Name"),
)

address_line_1 = models.CharField(
max_length=255,
verbose_name=_("Address Line 1"),
)

address_line_2 = models.CharField(
max_length=255,
verbose_name=_("Address Line 2"),
)

city = models.CharField(
max_length=255,
verbose_name=_("City"),
)

zip_code = models.CharField(
max_length=255,
verbose_name=_("Zip Code"),
)

country = models.CharField(
max_length=255,
verbose_name=_("Country"),
)

preferred_language = models.CharField(
max_length=255,
verbose_name=_("Preferred Language"),
)

tax_id = models.CharField(
max_length=255,
verbose_name=_("Tax ID"),
)

stripe_customer_id = models.CharField(
max_length=255,
verbose_name=_("Stripe Customer ID"),
blank=True,
null=True,
)

stripe_payment_method_id = models.CharField(
max_length=255,
verbose_name=_("Payment Method"),
blank=True,
null=True,
)

stripe_setup_intent_id = models.CharField(
max_length=255,
verbose_name=_("Setup Intent ID"),
blank=True,
null=True,
)

def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
self.organizer.cache.clear()

def save(self, *args, **kwargs):
super().save(*args, **kwargs)
self.organizer.cache.clear()
7 changes: 6 additions & 1 deletion src/pretix/base/services/mail.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import base64
import inspect
import logging
import os
Expand Down Expand Up @@ -277,7 +278,8 @@ def _create_mime_attachment(self, content, mimetype):
def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: str, sender: str,
event: int = None, position: int = None, headers: dict = None, bcc: List[str] = None,
invoices: List[int] = None, order: int = None, attach_tickets=False, user=None,
attach_ical=False, attach_cached_files: List[int] = None) -> bool:
attach_ical=False, attach_cached_files: List[int] = None, attach_file_base64: str = None,
attach_file_name: str = None) -> bool:
email = CustomEmail(subject, body, sender, to=to, bcc=bcc, headers=headers)
if html is not None:
html_message = SafeMIMEMultipart(_subtype='related', encoding=settings.DEFAULT_CHARSET)
Expand Down Expand Up @@ -385,6 +387,9 @@ def cm(): return scopes_disabled() # noqa
pass

email = global_email_filter.send_chained(event, 'message', message=email, user=user, order=order)
if attach_file_base64:
attach_file_content = base64.b64decode(attach_file_base64)
email.attach(attach_file_name, attach_file_content, "application/pdf")

try:
backend.send_messages([email])
Expand Down
Loading
Loading