Files
accounting-backend/accounting/serializers.py
2026-07-01 00:29:25 +03:30

221 lines
8.1 KiB
Python

from django.db import transaction
from rest_framework import serializers
from .models import (
Account,
CompanySettings,
Employee,
Invoice,
InvoiceLine,
Party,
Payslip,
Product,
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)
class Meta:
model = Party
fields = ['id', 'kind', 'name', 'phone', 'economicCode', 'address', 'openingBalance']
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 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
)
lines = InvoiceLineSerializer(many=True)
class Meta:
model = Invoice
fields = ['id', 'kind', 'number', 'partyId', '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
)
class Meta:
model = TreasuryTxn
fields = ['id', 'number', 'kind', 'method', 'date', 'partyId', 'amount', 'reference', 'description']
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