Initial commit: standalone Django accounting API with admin JWT authorization
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
220
accounting/serializers.py
Normal file
220
accounting/serializers.py
Normal file
@@ -0,0 +1,220 @@
|
||||
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
|
||||
Reference in New Issue
Block a user