def post(self, request, *args, **kwargs): transaction = self.get_object(**kwargs) requested_action = kwargs.get('requested_action') if requested_action not in self.allowed_actions: error_message = "{} is not an allowed".format(requested_action) return Response({"errors": error_message}, status=status.HTTP_400_BAD_REQUEST) action_to_execute = getattr(transaction, requested_action, None) if not action_to_execute: raise Http404 try: errors = action_to_execute() transaction.save() except TransitionNotAllowed: errors = "Can't execute action because the transaction is in an " \ "incorrect state: {}".format(transaction.state) if errors: return Response({"errors": errors}, status=status.HTTP_400_BAD_REQUEST) transaction_serialized = TransactionSerializer( transaction, context={'request': request}) return Response(transaction_serialized.data, status=status.HTTP_200_OK)
def get_transactions(self, document): if document.kind == 'invoice': transactions = document.invoice_transactions.all() elif document.kind == 'proforma': transactions = document.proforma_transactions.all() else: return [] for transaction in transactions: # This is done to avoid prefetching already prefetched resources transaction.payment_method.customer = document.customer transaction.provider = document.provider return TransactionSerializer(transactions, many=True, context=self.context).data
class ProformaSerializer(AutoCleanSerializerMixin, serializers.HyperlinkedModelSerializer): proforma_entries = DocumentEntrySerializer(many=True, required=False) pdf_url = PDFUrl(view_name='pdf', source='*', read_only=True) customer = CustomerUrl(view_name='customer-detail', queryset=Customer.objects.all()) transactions = TransactionSerializer(many=True, read_only=True) class Meta: model = Proforma fields = ('id', 'series', 'number', 'provider', 'customer', 'archived_provider', 'archived_customer', 'due_date', 'issue_date', 'paid_date', 'cancel_date', 'sales_tax_name', 'sales_tax_percent', 'currency', 'transaction_currency', 'transaction_xe_rate', 'transaction_xe_date', 'state', 'invoice', 'proforma_entries', 'total', 'total_in_transaction_currency', 'pdf_url', 'transactions') read_only_fields = ('archived_provider', 'archived_customer', 'total', 'total_in_transaction_currency') extra_kwargs = { 'transaction_currency': { 'required': False }, 'number': { 'required': False }, 'series': { 'required': False }, 'invoice': { 'source': 'related_document', 'view_name': 'invoice-detail' }, } def create(self, validated_data): entries = validated_data.pop('proforma_entries', []) proforma = Proforma.objects.create(**validated_data) for entry in entries: entry_dict = dict() entry_dict['proforma'] = proforma for field in entry.items(): entry_dict[field[0]] = field[1] DocumentEntry.objects.create(**entry_dict) return proforma def update(self, instance, validated_data): # The provider has changed => force the generation of the correct number # corresponding to the count of the new provider current_provider = instance.provider new_provider = validated_data.get('provider') if new_provider and new_provider != current_provider: instance.number = None updateable_fields = instance.updateable_fields for field_name in updateable_fields: field_value = validated_data.get(field_name, getattr(instance, field_name)) setattr(instance, field_name, field_value) instance.save() return instance def instantiate_object(self, data): proforma = super(ProformaSerializer, self).instantiate_object(data) # after clean_defaults is moved into full_clean this call won't be needed proforma.clean_defaults() return proforma def validate(self, data): data = super(ProformaSerializer, self).validate(data) if self.instance and 'state' in data and data[ 'state'] != self.instance.state: msg = "Direct state modification is not allowed." \ " Use the corresponding endpoint to update the state." raise serializers.ValidationError(msg) return data
class InvoiceSerializer(serializers.HyperlinkedModelSerializer): invoice_entries = DocumentEntrySerializer(many=True) pdf_url = PDFUrl(view_name='pdf', source='*', read_only=True) customer = CustomerPrimaryKey(queryset=Customer.objects.all()) provider = ProviderPrimaryKey(queryset=Provider.objects.all()) transactions = TransactionSerializer(many=True, read_only=True) class Meta: model = Invoice fields = ('id', 'series', 'number', 'provider', 'customer', 'archived_provider', 'archived_customer', 'due_date', 'issue_date', 'paid_date', 'cancel_date', 'sales_tax_name', 'sales_tax_percent', 'currency', 'transaction_currency', 'transaction_xe_rate', 'transaction_xe_date', 'state', 'proforma', 'invoice_entries', 'total', 'total_in_transaction_currency', 'pdf_url', 'transactions') read_only_fields = ('archived_provider', 'archived_customer', 'total', 'total_in_transaction_currency') extra_kwargs = { 'transaction_currency': { 'required': False }, 'proforma': { 'source': 'related_document', 'view_name': 'proforma-detail' } } def create(self, validated_data): entries = validated_data.pop('invoice_entries', None) # Create the new invoice object invoice = Invoice.objects.create(**validated_data) # Add the invoice entries for entry in entries: entry_dict = dict() entry_dict['invoice'] = invoice for field in entry.items(): entry_dict[field[0]] = field[1] DocumentEntry.objects.create(**entry_dict) return invoice def update(self, instance, validated_data): # The provider has changed => force the generation of the correct number # corresponding to the count of the new provider current_provider = instance.provider new_provider = validated_data.get('provider') if new_provider and new_provider != current_provider: instance.number = None updateable_fields = instance.updateable_fields for field_name in updateable_fields: field_value = validated_data.get(field_name, getattr(instance, field_name)) setattr(instance, field_name, field_value) instance.save() return instance def validate(self, data): data = super(InvoiceSerializer, self).validate(data) if self.instance: self.instance.clean() if self.instance and data['state'] != self.instance.state: msg = "Direct state modification is not allowed." \ " Use the corresponding endpoint to update the state." raise serializers.ValidationError(msg) return data
def post(self, request, *args, **kwargs): """ Create a one off transaction by way of creating an invoice, payment method and document, and return the created transaction. """ rq = request.data customer_one_off_defaults = { "currency": "USD", } ## Customer creation # # Check to see if we're getting an `account_id`, and try to # retrieve that customer first. # customer = None has_uuid = rq.get('customer', {}).get('account_id', False) if has_uuid: _u = uuid.UUID(has_uuid) # This will raise Customer.DoesNotExist if an invalid UUID is given customer = Customer.objects.get(account_id=_u) # If we have no customer and no exceptions were raised, create a # new customer # if customer == None: new_customer = customer_one_off_defaults new_customer.update(**rq.get('customer')) customer = Customer(**new_customer) customer.save() ## Create a customer payment method # payment_processor_name = rq.get("payment_processor", "manual") # Check if a method for the customer with this payment_processor # exists already # try: has_method = PaymentMethod.objects.get( customer=customer, payment_processor=payment_processor_name) except PaymentMethod.DoesNotExist: has_method = False # Create a new method # if not has_method: # TODO: what are our sensible defaults? customer_default_payment_method = { "customer": customer, "payment_processor": payment_processor_name, "verified": True, "canceled": False, "valid_until": dt.now() + timedelta(days=7), "display_info": "pytest", "data": json.dumps({ "attempt_retries_after": 2, "stop_retry_attempts": 5 }) } new_pm = PaymentMethod(**customer_default_payment_method) new_pm.save() else: new_pm = has_method ## Get a provider # First we'll try to get our internal provider, otherwise create # it if it doesn't exist. # # TODO: add a request parameter for the provider name or # something. # provider = Provider.objects.filter( invoice_series="BPInvoiceSeries").first() if provider is None: prv = { "name": "Internal Billing Provider", "company": "Internal Billing Provider", "invoice_series": "BPInvoiceSeries", "flow": "invoice", "email": "", "address_1": "1 Mulberry Lane", "address_2": "", "city": "Pacoima", "state": "CA", "zip_code": "", "country": "US", "invoice_starting_number": 1 } provider = Provider(**prv) provider.save() ## Create an invoice # Some defaults to save effort from the client user # invoice_one_off_defaults = { "provider": provider, "series": provider.invoice_series, "customer": customer, "transaction_currency": "USD", "transaction_xe_rate": Decimal('1.0000'), "currency": "USD", "state": "draft", } invoice_entry_defaults = { "quantity": 1.0, "unit_price": rq.get("amount", 0.0), "start_date": None, "end_date": None, "prorated": False, "product_code": None } # Override these with any request data # new_entry = invoice_entry_defaults.copy() new_invoice = invoice_one_off_defaults.copy() new_invoice.update(**rq.get("invoice", {})) if 'issue_date' in new_invoice: new_invoice['issue_date'] = dtparse( new_invoice.get('issue_date')).date() # Create the invoice inv = Invoice(**new_invoice) inv.save() # Add the entry (save first, or else) entr = DocumentEntry(**new_entry) entr.save() inv.invoice_entries.add(entr) inv.save() # Issue the invoice to generate transactions. inv.issue() inv.save() transaction = Transaction.objects.filter(invoice=inv).first() _ser_kwargs = {"context": {"request": request}} return Response({ "customer": CustomerSerializer(customer, **_ser_kwargs).data, "payment_method": PaymentMethodSerializer(new_pm, **_ser_kwargs).data, "provider": ProviderSerializer(provider, **_ser_kwargs).data, "invoice": InvoiceSerializer(inv, **_ser_kwargs).data, "transaction": TransactionSerializer(transaction, **_ser_kwargs).data, })