from django.db import transaction from rest_framework import serializers from .models import ( Account, CompanySettings, Employee, Invoice, InvoiceLine, Party, PartyAdjustment, Payslip, Product, SalesType, TreasuryTxn, Voucher, VoucherLine, ) class AccountSerializer(serializers.ModelSerializer): parentId = serializers.PrimaryKeyRelatedField( source='parent', queryset=Account.objects.all(), allow_null=True, required=False ) isGroup = serializers.BooleanField(source='is_group', required=False) openingBalance = serializers.FloatField(source='opening_balance', required=False) class Meta: model = Account fields = ['id', 'code', 'name', 'type', 'parentId', 'isGroup', 'openingBalance'] class PartySerializer(serializers.ModelSerializer): economicCode = serializers.CharField(source='economic_code', required=False, allow_blank=True) openingBalance = serializers.FloatField(source='opening_balance', required=False) externalId = serializers.UUIDField(source='external_id', required=False, allow_null=True) externalSource = serializers.CharField(source='external_source', required=False, allow_blank=True) firstName = serializers.CharField(source='first_name', required=False, allow_blank=True) lastName = serializers.CharField(source='last_name', required=False, allow_blank=True) nationalCode = serializers.CharField(source='national_code', required=False, allow_blank=True) walletBalance = serializers.FloatField(source='wallet_balance', required=False) isActive = serializers.BooleanField(source='is_active', required=False) isVerified = serializers.BooleanField(source='is_verified', required=False) class Meta: model = Party fields = [ 'id', 'kind', 'name', 'phone', 'economicCode', 'address', 'openingBalance', 'externalId', 'externalSource', 'firstName', 'lastName', 'username', 'email', 'nationalCode', 'iban', 'walletBalance', 'isActive', 'isVerified', 'birthday', 'venues', ] class ProductSerializer(serializers.ModelSerializer): salePrice = serializers.FloatField(source='sale_price', required=False) purchasePrice = serializers.FloatField(source='purchase_price', required=False) reorderLevel = serializers.FloatField(source='reorder_level', required=False) class Meta: model = Product fields = ['id', 'code', 'name', 'unit', 'salePrice', 'purchasePrice', 'stock', 'reorderLevel'] class VoucherLineSerializer(serializers.ModelSerializer): # Lenient: accepts client-generated ids (ignored on write, recreated server-side). id = serializers.CharField(required=False) accountId = serializers.PrimaryKeyRelatedField(source='account', queryset=Account.objects.all()) class Meta: model = VoucherLine fields = ['id', 'accountId', 'description', 'debit', 'credit'] class VoucherSerializer(serializers.ModelSerializer): lines = VoucherLineSerializer(many=True) class Meta: model = Voucher fields = ['id', 'number', 'date', 'description', 'status', 'source', 'lines'] @transaction.atomic def create(self, validated_data): lines = validated_data.pop('lines', []) voucher = Voucher.objects.create(**validated_data) for line in lines: line.pop('id', None) VoucherLine.objects.create(voucher=voucher, **line) return voucher @transaction.atomic def update(self, instance, validated_data): lines = validated_data.pop('lines', None) for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() if lines is not None: instance.lines.all().delete() for line in lines: line.pop('id', None) VoucherLine.objects.create(voucher=instance, **line) return instance class SalesTypeSerializer(serializers.ModelSerializer): class Meta: model = SalesType fields = ['id', 'code', 'name', 'description', 'active'] class PartyAdjustmentSerializer(serializers.ModelSerializer): partyId = serializers.PrimaryKeyRelatedField(source='party', queryset=Party.objects.all()) class Meta: model = PartyAdjustment fields = ['id', 'number', 'partyId', 'kind', 'date', 'amount', 'description'] class InvoiceLineSerializer(serializers.ModelSerializer): # Lenient: accepts client-generated ids (ignored on write, recreated server-side). id = serializers.CharField(required=False) productId = serializers.PrimaryKeyRelatedField( source='product', queryset=Product.objects.all(), allow_null=True, required=False ) unitPrice = serializers.FloatField(source='unit_price', required=False) taxRate = serializers.FloatField(source='tax_rate', required=False) class Meta: model = InvoiceLine fields = ['id', 'productId', 'quantity', 'unitPrice', 'discount', 'taxRate'] class InvoiceSerializer(serializers.ModelSerializer): partyId = serializers.PrimaryKeyRelatedField( source='party', queryset=Party.objects.all(), allow_null=True, required=False ) documentType = serializers.CharField(source='document_type', required=False) salesTypeId = serializers.PrimaryKeyRelatedField( source='sales_type', queryset=SalesType.objects.all(), allow_null=True, required=False ) relatedInvoiceId = serializers.PrimaryKeyRelatedField( source='related_invoice', queryset=Invoice.objects.all(), allow_null=True, required=False ) lines = InvoiceLineSerializer(many=True) class Meta: model = Invoice fields = [ 'id', 'kind', 'documentType', 'number', 'partyId', 'salesTypeId', 'relatedInvoiceId', 'date', 'status', 'note', 'lines', ] @transaction.atomic def create(self, validated_data): lines = validated_data.pop('lines', []) invoice = Invoice.objects.create(**validated_data) for line in lines: line.pop('id', None) InvoiceLine.objects.create(invoice=invoice, **line) return invoice @transaction.atomic def update(self, instance, validated_data): lines = validated_data.pop('lines', None) for attr, value in validated_data.items(): setattr(instance, attr, value) instance.save() if lines is not None: instance.lines.all().delete() for line in lines: line.pop('id', None) InvoiceLine.objects.create(invoice=instance, **line) return instance class TreasuryTxnSerializer(serializers.ModelSerializer): partyId = serializers.PrimaryKeyRelatedField( source='party', queryset=Party.objects.all(), allow_null=True, required=False ) amount = serializers.FloatField(required=False) taxAmount = serializers.FloatField(source='tax_amount', required=False) platformProfitAmount = serializers.FloatField(source='platform_profit_amount', required=False) ownerNetAmount = serializers.FloatField(source='owner_net_amount', required=False) class Meta: model = TreasuryTxn fields = [ 'id', 'number', 'kind', 'method', 'date', 'partyId', 'amount', 'taxAmount', 'platformProfitAmount', 'ownerNetAmount', 'reference', 'description', 'source', ] class EmployeeSerializer(serializers.ModelSerializer): baseSalary = serializers.FloatField(source='base_salary', required=False) insuranceNo = serializers.CharField(source='insurance_no', required=False, allow_blank=True) hireDate = serializers.DateField(source='hire_date', required=False, allow_null=True) class Meta: model = Employee fields = ['id', 'code', 'name', 'position', 'baseSalary', 'insuranceNo', 'hireDate', 'active'] class PayslipSerializer(serializers.ModelSerializer): employeeId = serializers.PrimaryKeyRelatedField(source='employee', queryset=Employee.objects.all()) baseSalary = serializers.FloatField(source='base_salary', required=False) class Meta: model = Payslip fields = [ 'id', 'employeeId', 'period', 'baseSalary', 'overtime', 'bonus', 'deductions', 'insurance', 'tax', ] class CompanySettingsSerializer(serializers.ModelSerializer): economicCode = serializers.CharField(source='economic_code', required=False, allow_blank=True) fiscalYear = serializers.CharField(source='fiscal_year', required=False, allow_blank=True) baseCurrency = serializers.CharField(source='base_currency', required=False, allow_blank=True) taxRate = serializers.FloatField(source='tax_rate', required=False) voucherAccounts = serializers.SerializerMethodField() _MAPPING_FIELDS = { 'bankAccountId': 'bank_account_id', 'ownerPayableAccountId': 'owner_payable_account_id', 'customerPayableAccountId': 'customer_payable_account_id', 'ownerShareAccountId': 'owner_share_account_id', 'salesIncomeAccountId': 'sales_income_account_id', } class Meta: model = CompanySettings fields = [ 'name', 'economicCode', 'fiscalYear', 'baseCurrency', 'taxRate', 'address', 'phone', 'voucherAccounts', ] def get_voucherAccounts(self, obj): def to_id(value): return str(value) if value else '' return { 'bankAccountId': to_id(obj.bank_account_id), 'ownerPayableAccountId': to_id(obj.owner_payable_account_id), 'customerPayableAccountId': to_id(obj.customer_payable_account_id), 'ownerShareAccountId': to_id(obj.owner_share_account_id), 'salesIncomeAccountId': to_id(obj.sales_income_account_id), } def _apply_voucher_accounts(self, instance): mapping = self.initial_data.get('voucherAccounts') if not isinstance(mapping, dict): return valid_ids = {str(v) for v in Account.objects.values_list('id', flat=True)} for camel_key, model_attr in self._MAPPING_FIELDS.items(): if camel_key not in mapping: continue raw = mapping.get(camel_key) setattr(instance, model_attr, raw if raw in valid_ids else None) @transaction.atomic def update(self, instance, validated_data): for attr, value in validated_data.items(): setattr(instance, attr, value) self._apply_voucher_accounts(instance) instance.save() return instance