293 lines
12 KiB
Python
293 lines
12 KiB
Python
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)
|
|
regarding = serializers.CharField(required=False, allow_blank=True)
|
|
|
|
class Meta:
|
|
model = Voucher
|
|
fields = ['id', 'number', 'date', 'regarding', 'description', 'status', 'source', 'lines']
|
|
read_only_fields = ['number']
|
|
|
|
@staticmethod
|
|
def _next_number() -> int:
|
|
last_number = (
|
|
Voucher.objects.select_for_update()
|
|
.order_by('-number')
|
|
.values_list('number', flat=True)
|
|
.first()
|
|
or 0
|
|
)
|
|
return last_number + 1
|
|
|
|
@transaction.atomic
|
|
def create(self, validated_data):
|
|
lines = validated_data.pop('lines', [])
|
|
validated_data.setdefault('regarding', '')
|
|
validated_data['number'] = self._next_number()
|
|
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)
|
|
# Keep voucher numbers immutable after creation.
|
|
validated_data.pop('number', None)
|
|
if 'regarding' in validated_data and validated_data['regarding'] is None:
|
|
validated_data['regarding'] = ''
|
|
if instance.number <= 0:
|
|
instance.number = self._next_number()
|
|
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)
|
|
eventName = serializers.CharField(source='event_name', required=False, allow_blank=True)
|
|
|
|
class Meta:
|
|
model = InvoiceLine
|
|
fields = ['id', 'productId', 'quantity', 'unitPrice', 'discount', 'taxRate', 'eventName']
|
|
|
|
|
|
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)
|
|
line.setdefault('event_name', '')
|
|
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)
|
|
line.setdefault('event_name', '')
|
|
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)
|
|
eventName = serializers.CharField(source='event_name', required=False, allow_blank=True)
|
|
|
|
class Meta:
|
|
model = TreasuryTxn
|
|
fields = [
|
|
'id', 'number', 'kind', 'method', 'date', 'partyId', 'amount',
|
|
'taxAmount', 'platformProfitAmount', 'ownerNetAmount',
|
|
'reference', 'description', 'eventName', '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
|