|
1 | 1 | from api.types import BaseErrorType |
| 2 | +from api.utils import validate_email |
2 | 3 | from strawberry.scalars import JSON |
3 | 4 |
|
4 | 5 | from datetime import datetime |
|
25 | 26 | from conferences.models.conference import Conference |
26 | 27 | from badges.roles import ConferenceRole, get_conference_roles_for_ticket_data |
27 | 28 | from api.helpers.ids import encode_hashid |
| 29 | +from countries import countries |
| 30 | +from billing.validation import ( |
| 31 | + validate_italian_zip_code, |
| 32 | + validate_fiscal_code, |
| 33 | + validate_italian_vat_number, |
| 34 | + validate_sdi_code, |
| 35 | +) |
| 36 | +from billing.exceptions import ( |
| 37 | + ItalianZipCodeValidationError, |
| 38 | + FiscalCodeValidationError, |
| 39 | + ItalianVatNumberValidationError, |
| 40 | + SdiValidationError, |
| 41 | +) |
28 | 42 |
|
29 | 43 |
|
30 | 44 | @strawberry.type |
@@ -475,3 +489,214 @@ def to_json(self): |
475 | 489 | data["answers"] = [answer.to_json() for answer in self.answers] |
476 | 490 |
|
477 | 491 | return data |
| 492 | + |
| 493 | + |
| 494 | +@strawberry.type |
| 495 | +class InvoiceInformationErrors: |
| 496 | + company: list[str] = strawberry.field(default_factory=list) |
| 497 | + given_name: list[str] = strawberry.field(default_factory=list) |
| 498 | + family_name: list[str] = strawberry.field(default_factory=list) |
| 499 | + street: list[str] = strawberry.field(default_factory=list) |
| 500 | + zipcode: list[str] = strawberry.field(default_factory=list) |
| 501 | + city: list[str] = strawberry.field(default_factory=list) |
| 502 | + country: list[str] = strawberry.field(default_factory=list) |
| 503 | + vat_id: list[str] = strawberry.field(default_factory=list) |
| 504 | + fiscal_code: list[str] = strawberry.field(default_factory=list) |
| 505 | + pec: list[str] = strawberry.field(default_factory=list) |
| 506 | + sdi: list[str] = strawberry.field(default_factory=list) |
| 507 | + |
| 508 | + |
| 509 | +@strawberry.type |
| 510 | +class CreateOrderTicketErrors: |
| 511 | + attendee_name: AttendeeNameInputError = strawberry.field( |
| 512 | + default_factory=AttendeeNameInputError |
| 513 | + ) |
| 514 | + attendee_email: list[str] = strawberry.field(default_factory=list) |
| 515 | + |
| 516 | + |
| 517 | +@strawberry.type |
| 518 | +class CreateOrderErrors(BaseErrorType): |
| 519 | + @strawberry.type |
| 520 | + class _CreateOrderErrors: |
| 521 | + invoice_information: InvoiceInformationErrors = strawberry.field( |
| 522 | + default_factory=InvoiceInformationErrors |
| 523 | + ) |
| 524 | + tickets: list[CreateOrderTicketErrors] = strawberry.field(default_factory=list) |
| 525 | + non_field_errors: list[str] = strawberry.field(default_factory=list) |
| 526 | + |
| 527 | + errors: _CreateOrderErrors = None |
| 528 | + |
| 529 | + |
| 530 | +@strawberry.input |
| 531 | +class CreateOrderTicketAnswer: |
| 532 | + question_id: str |
| 533 | + value: str |
| 534 | + |
| 535 | + |
| 536 | +@strawberry.input |
| 537 | +class CreateOrderTicket: |
| 538 | + ticket_id: str |
| 539 | + attendee_name: AttendeeNameInput |
| 540 | + attendee_email: str |
| 541 | + variation: Optional[str] = None |
| 542 | + answers: Optional[List[CreateOrderTicketAnswer]] = None |
| 543 | + voucher: Optional[str] = None |
| 544 | + |
| 545 | + def validate( |
| 546 | + self, errors: CreateOrderErrors, is_admission: bool |
| 547 | + ) -> CreateOrderErrors: |
| 548 | + if not is_admission: |
| 549 | + return errors |
| 550 | + |
| 551 | + with errors.with_prefix("attendee_name"): |
| 552 | + self.attendee_name.validate(errors) |
| 553 | + |
| 554 | + if not self.attendee_email.strip(): |
| 555 | + errors.add_error("attendee_email", "This field is required") |
| 556 | + elif not validate_email(self.attendee_email): |
| 557 | + errors.add_error("attendee_email", "Invalid email address") |
| 558 | + |
| 559 | + return errors |
| 560 | + |
| 561 | + |
| 562 | +@strawberry.input |
| 563 | +class InvoiceInformation: |
| 564 | + is_business: bool |
| 565 | + company: Optional[str] |
| 566 | + given_name: str |
| 567 | + family_name: str |
| 568 | + street: str |
| 569 | + zipcode: str |
| 570 | + city: str |
| 571 | + country: str |
| 572 | + vat_id: str |
| 573 | + fiscal_code: str |
| 574 | + pec: str | None = None |
| 575 | + sdi: str | None = None |
| 576 | + |
| 577 | + def validate(self, errors: CreateOrderErrors) -> CreateOrderErrors: |
| 578 | + required_fields = [ |
| 579 | + "given_name", |
| 580 | + "family_name", |
| 581 | + "street", |
| 582 | + "zipcode", |
| 583 | + "city", |
| 584 | + "country", |
| 585 | + ] |
| 586 | + |
| 587 | + if self.is_business: |
| 588 | + required_fields += ["vat_id", "company"] |
| 589 | + |
| 590 | + if self.country == "IT": |
| 591 | + if self.is_business: |
| 592 | + required_fields += ["sdi"] |
| 593 | + else: |
| 594 | + required_fields += ["fiscal_code"] |
| 595 | + |
| 596 | + for required_field in required_fields: |
| 597 | + value = getattr(self, required_field) |
| 598 | + |
| 599 | + if not value: |
| 600 | + errors.add_error( |
| 601 | + required_field, |
| 602 | + "This field is required", |
| 603 | + ) |
| 604 | + |
| 605 | + self.validate_country(errors) |
| 606 | + |
| 607 | + if self.country == "IT": |
| 608 | + self.validate_italian_zip_code(errors) |
| 609 | + self.validate_pec(errors) |
| 610 | + |
| 611 | + if self.is_business: |
| 612 | + self.validate_sdi(errors) |
| 613 | + self.validate_partita_iva(errors) |
| 614 | + else: |
| 615 | + self.validate_fiscal_code(errors) |
| 616 | + |
| 617 | + return errors |
| 618 | + |
| 619 | + def validate_country(self, errors: CreateOrderErrors): |
| 620 | + if not self.country: |
| 621 | + return |
| 622 | + |
| 623 | + if not countries.is_valid(self.country): |
| 624 | + errors.add_error( |
| 625 | + "country", |
| 626 | + "Invalid country", |
| 627 | + ) |
| 628 | + |
| 629 | + def validate_pec(self, errors: CreateOrderErrors): |
| 630 | + if not self.pec: |
| 631 | + return |
| 632 | + |
| 633 | + if not validate_email(self.pec): |
| 634 | + errors.add_error("pec", "Invalid PEC address") |
| 635 | + |
| 636 | + def validate_fiscal_code(self, errors: CreateOrderErrors): |
| 637 | + if not self.fiscal_code: |
| 638 | + return |
| 639 | + |
| 640 | + try: |
| 641 | + validate_fiscal_code(self.fiscal_code) |
| 642 | + except FiscalCodeValidationError as exc: |
| 643 | + errors.add_error("fiscal_code", str(exc)) |
| 644 | + |
| 645 | + def validate_partita_iva(self, errors: CreateOrderErrors): |
| 646 | + if not self.vat_id: |
| 647 | + return |
| 648 | + try: |
| 649 | + validate_italian_vat_number(self.vat_id) |
| 650 | + except ItalianVatNumberValidationError as exc: |
| 651 | + errors.add_error("vat_id", str(exc)) |
| 652 | + |
| 653 | + def validate_italian_zip_code(self, errors: CreateOrderErrors): |
| 654 | + if not self.zipcode: |
| 655 | + return |
| 656 | + |
| 657 | + try: |
| 658 | + validate_italian_zip_code(self.zipcode) |
| 659 | + except ItalianZipCodeValidationError as exc: |
| 660 | + errors.add_error("zipcode", str(exc)) |
| 661 | + |
| 662 | + def validate_sdi(self, errors: CreateOrderErrors): |
| 663 | + if not self.sdi: |
| 664 | + return |
| 665 | + |
| 666 | + try: |
| 667 | + validate_sdi_code(self.sdi) |
| 668 | + except SdiValidationError as exc: |
| 669 | + errors.add_error("sdi", str(exc)) |
| 670 | + |
| 671 | + |
| 672 | +@strawberry.input |
| 673 | +class CreateOrderInput: |
| 674 | + email: str |
| 675 | + locale: str |
| 676 | + payment_provider: str |
| 677 | + invoice_information: InvoiceInformation |
| 678 | + tickets: list[CreateOrderTicket] |
| 679 | + |
| 680 | + def validate(self, conference) -> CreateOrderErrors: |
| 681 | + # Import here to avoid circular dependency |
| 682 | + from pretix import get_items |
| 683 | + |
| 684 | + pretix_items = get_items(conference) |
| 685 | + |
| 686 | + errors = CreateOrderErrors() |
| 687 | + |
| 688 | + with errors.with_prefix("invoice_information"): |
| 689 | + self.invoice_information.validate(errors) |
| 690 | + |
| 691 | + for index, ticket in enumerate(self.tickets): |
| 692 | + with errors.with_prefix("tickets", index): |
| 693 | + is_admission = pretix_items[ticket.ticket_id]["admission"] |
| 694 | + ticket.validate(errors, is_admission) |
| 695 | + |
| 696 | + return errors.if_has_errors |
| 697 | + |
| 698 | + |
| 699 | +@strawberry.type |
| 700 | +class Order: |
| 701 | + code: str |
| 702 | + payment_url: str |
0 commit comments