375 lines
13 KiB
Python
375 lines
13 KiB
Python
import uuid
|
|
|
|
from django.db import models
|
|
|
|
|
|
class Account(models.Model):
|
|
"""A node in the chart of accounts (کدینگ حسابها)."""
|
|
|
|
ACCOUNT_TYPES = [
|
|
('asset', 'Asset'),
|
|
('liability', 'Liability'),
|
|
('equity', 'Equity'),
|
|
('income', 'Income'),
|
|
('expense', 'Expense'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
code = models.CharField(max_length=20, unique=True, db_index=True)
|
|
name = models.CharField(max_length=150)
|
|
type = models.CharField(max_length=20, choices=ACCOUNT_TYPES)
|
|
parent = models.ForeignKey(
|
|
'self', on_delete=models.SET_NULL, null=True, blank=True, related_name='children'
|
|
)
|
|
is_group = models.BooleanField(default=False)
|
|
opening_balance = models.FloatField(default=0)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_accounts'
|
|
ordering = ['code']
|
|
|
|
def __str__(self):
|
|
return f"{self.code} - {self.name}"
|
|
|
|
|
|
class Party(models.Model):
|
|
"""A trading partner (customer or supplier) used by invoices and treasury."""
|
|
|
|
PARTY_KINDS = [
|
|
('customer', 'Customer'),
|
|
('supplier', 'Supplier'),
|
|
]
|
|
EXTERNAL_SOURCES = [
|
|
('', 'Manual'),
|
|
('funzone_customer', 'FunZone Customer'),
|
|
('funzone_owner', 'FunZone Owner'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
kind = models.CharField(max_length=20, choices=PARTY_KINDS)
|
|
name = models.CharField(max_length=150)
|
|
phone = models.CharField(max_length=30, blank=True, default='')
|
|
economic_code = models.CharField(max_length=50, blank=True, default='')
|
|
address = models.TextField(blank=True, default='')
|
|
opening_balance = models.FloatField(default=0)
|
|
external_id = models.UUIDField(null=True, blank=True, db_index=True)
|
|
external_source = models.CharField(
|
|
max_length=30, choices=EXTERNAL_SOURCES, blank=True, default=''
|
|
)
|
|
first_name = models.CharField(max_length=100, blank=True, default='')
|
|
last_name = models.CharField(max_length=100, blank=True, default='')
|
|
username = models.CharField(max_length=100, blank=True, default='')
|
|
email = models.EmailField(blank=True, default='')
|
|
national_code = models.CharField(max_length=20, blank=True, default='')
|
|
iban = models.CharField(max_length=34, blank=True, default='')
|
|
wallet_balance = models.FloatField(default=0)
|
|
is_active = models.BooleanField(default=True)
|
|
is_verified = models.BooleanField(default=False)
|
|
birthday = models.DateField(null=True, blank=True)
|
|
venues = models.JSONField(default=list, blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_parties'
|
|
ordering = ['name']
|
|
constraints = [
|
|
models.UniqueConstraint(
|
|
fields=['external_id', 'external_source'],
|
|
condition=models.Q(external_id__isnull=False),
|
|
name='unique_external_party',
|
|
),
|
|
]
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class Product(models.Model):
|
|
"""An inventory item."""
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
code = models.CharField(max_length=50, db_index=True)
|
|
name = models.CharField(max_length=150)
|
|
unit = models.CharField(max_length=30, blank=True, default='')
|
|
sale_price = models.FloatField(default=0)
|
|
purchase_price = models.FloatField(default=0)
|
|
stock = models.FloatField(default=0)
|
|
reorder_level = models.FloatField(default=0)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_products'
|
|
ordering = ['name']
|
|
|
|
def __str__(self):
|
|
return f"{self.code} - {self.name}"
|
|
|
|
|
|
class Voucher(models.Model):
|
|
"""A double-entry journal voucher (سند حسابداری)."""
|
|
|
|
STATUS_CHOICES = [
|
|
('draft', 'Draft'),
|
|
('posted', 'Posted'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
number = models.PositiveIntegerField(default=0)
|
|
date = models.DateField()
|
|
description = models.TextField(blank=True, default='')
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
|
|
source = models.CharField(max_length=120, blank=True, default='', db_index=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_vouchers'
|
|
ordering = ['-date', '-number']
|
|
|
|
def __str__(self):
|
|
return f"Voucher #{self.number}"
|
|
|
|
|
|
class VoucherLine(models.Model):
|
|
"""A single debit/credit line within a voucher."""
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
voucher = models.ForeignKey(Voucher, on_delete=models.CASCADE, related_name='lines')
|
|
account = models.ForeignKey(Account, on_delete=models.PROTECT, related_name='voucher_lines')
|
|
description = models.CharField(max_length=255, blank=True, default='')
|
|
debit = models.FloatField(default=0)
|
|
credit = models.FloatField(default=0)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_voucher_lines'
|
|
|
|
def __str__(self):
|
|
return f"Line {self.account_id} D:{self.debit} C:{self.credit}"
|
|
|
|
|
|
class SalesType(models.Model):
|
|
"""A sales category (نوع فروش) referenced by sales documents."""
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
code = models.CharField(max_length=50, db_index=True)
|
|
name = models.CharField(max_length=150)
|
|
description = models.TextField(blank=True, default='')
|
|
active = models.BooleanField(default=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_sales_types'
|
|
ordering = ['code']
|
|
|
|
def __str__(self):
|
|
return f"{self.code} - {self.name}"
|
|
|
|
|
|
class PartyAdjustment(models.Model):
|
|
"""Debit/credit notice adjusting a customer receivable (اعلامیه بدهکار/بستانکار)."""
|
|
|
|
ADJUSTMENT_KINDS = [
|
|
('debit', 'Debit'),
|
|
('credit', 'Credit'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
number = models.PositiveIntegerField(default=0)
|
|
party = models.ForeignKey(
|
|
Party, on_delete=models.CASCADE, related_name='adjustments'
|
|
)
|
|
kind = models.CharField(max_length=20, choices=ADJUSTMENT_KINDS)
|
|
date = models.DateField()
|
|
amount = models.FloatField(default=0)
|
|
description = models.TextField(blank=True, default='')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_party_adjustments'
|
|
ordering = ['-date', '-number']
|
|
|
|
def __str__(self):
|
|
return f"{self.kind} notice #{self.number}"
|
|
|
|
|
|
class Invoice(models.Model):
|
|
"""A sales or purchase invoice (فاکتور)."""
|
|
|
|
INVOICE_KINDS = [
|
|
('sale', 'Sale'),
|
|
('purchase', 'Purchase'),
|
|
]
|
|
DOCUMENT_TYPES = [
|
|
('proforma', 'Proforma'),
|
|
('invoice', 'Invoice'),
|
|
('return', 'Return'),
|
|
]
|
|
STATUS_CHOICES = [
|
|
('draft', 'Draft'),
|
|
('confirmed', 'Confirmed'),
|
|
('paid', 'Paid'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
kind = models.CharField(max_length=20, choices=INVOICE_KINDS)
|
|
document_type = models.CharField(
|
|
max_length=20, choices=DOCUMENT_TYPES, default='invoice'
|
|
)
|
|
number = models.PositiveIntegerField(default=0)
|
|
party = models.ForeignKey(
|
|
Party, on_delete=models.SET_NULL, null=True, blank=True, related_name='invoices'
|
|
)
|
|
sales_type = models.ForeignKey(
|
|
SalesType, on_delete=models.SET_NULL, null=True, blank=True, related_name='invoices'
|
|
)
|
|
related_invoice = models.ForeignKey(
|
|
'self', on_delete=models.SET_NULL, null=True, blank=True, related_name='derived_docs'
|
|
)
|
|
date = models.DateField()
|
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
|
|
note = models.TextField(blank=True, default='')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_invoices'
|
|
ordering = ['-date', '-number']
|
|
|
|
def __str__(self):
|
|
return f"{self.kind} invoice #{self.number}"
|
|
|
|
|
|
class InvoiceLine(models.Model):
|
|
"""A line item within an invoice."""
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
invoice = models.ForeignKey(Invoice, on_delete=models.CASCADE, related_name='lines')
|
|
product = models.ForeignKey(
|
|
Product, on_delete=models.PROTECT, null=True, blank=True, related_name='invoice_lines'
|
|
)
|
|
quantity = models.FloatField(default=0)
|
|
unit_price = models.FloatField(default=0)
|
|
discount = models.FloatField(default=0)
|
|
tax_rate = models.FloatField(default=0)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_invoice_lines'
|
|
|
|
def __str__(self):
|
|
return f"Line {self.product_id} x{self.quantity}"
|
|
|
|
|
|
class TreasuryTxn(models.Model):
|
|
"""A treasury receipt or payment (دریافت/پرداخت)."""
|
|
|
|
KIND_CHOICES = [
|
|
('receipt', 'Receipt'),
|
|
('payment', 'Payment'),
|
|
]
|
|
METHOD_CHOICES = [
|
|
('cash', 'Cash'),
|
|
('bank', 'Bank'),
|
|
('cheque', 'Cheque'),
|
|
]
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
number = models.PositiveIntegerField(default=0)
|
|
kind = models.CharField(max_length=20, choices=KIND_CHOICES)
|
|
method = models.CharField(max_length=20, choices=METHOD_CHOICES, default='bank')
|
|
date = models.DateField()
|
|
party = models.ForeignKey(
|
|
Party, on_delete=models.SET_NULL, null=True, blank=True, related_name='treasury_txns'
|
|
)
|
|
amount = models.FloatField(default=0)
|
|
tax_amount = models.FloatField(default=0)
|
|
platform_profit_amount = models.FloatField(default=0)
|
|
owner_net_amount = models.FloatField(default=0)
|
|
reference = models.CharField(max_length=120, blank=True, default='')
|
|
description = models.TextField(blank=True, default='')
|
|
source = models.CharField(max_length=120, blank=True, default='')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_treasury_txns'
|
|
ordering = ['-date', '-number']
|
|
|
|
def __str__(self):
|
|
return f"{self.kind} #{self.number}"
|
|
|
|
|
|
class Employee(models.Model):
|
|
"""A payroll employee."""
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
code = models.CharField(max_length=50, blank=True, default='')
|
|
name = models.CharField(max_length=150)
|
|
position = models.CharField(max_length=120, blank=True, default='')
|
|
base_salary = models.FloatField(default=0)
|
|
insurance_no = models.CharField(max_length=50, blank=True, default='')
|
|
hire_date = models.DateField(null=True, blank=True)
|
|
active = models.BooleanField(default=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_employees'
|
|
ordering = ['name']
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class Payslip(models.Model):
|
|
"""A monthly payslip for an employee (فیش حقوقی)."""
|
|
|
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
employee = models.ForeignKey(Employee, on_delete=models.CASCADE, related_name='payslips')
|
|
period = models.CharField(max_length=20, blank=True, default='')
|
|
base_salary = models.FloatField(default=0)
|
|
overtime = models.FloatField(default=0)
|
|
bonus = models.FloatField(default=0)
|
|
deductions = models.FloatField(default=0)
|
|
insurance = models.FloatField(default=0)
|
|
tax = models.FloatField(default=0)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_payslips'
|
|
ordering = ['-period']
|
|
|
|
def __str__(self):
|
|
return f"Payslip {self.employee_id} - {self.period}"
|
|
|
|
|
|
class CompanySettings(models.Model):
|
|
"""Singleton holding company info and the auto-voucher account mapping."""
|
|
|
|
id = models.PositiveSmallIntegerField(primary_key=True, default=1)
|
|
name = models.CharField(max_length=150, blank=True, default='')
|
|
economic_code = models.CharField(max_length=50, blank=True, default='')
|
|
fiscal_year = models.CharField(max_length=20, blank=True, default='')
|
|
base_currency = models.CharField(max_length=20, blank=True, default='تومان')
|
|
tax_rate = models.FloatField(default=0)
|
|
address = models.TextField(blank=True, default='')
|
|
phone = models.CharField(max_length=40, blank=True, default='')
|
|
|
|
bank_account = models.ForeignKey(
|
|
Account, on_delete=models.SET_NULL, null=True, blank=True, related_name='+'
|
|
)
|
|
owner_payable_account = models.ForeignKey(
|
|
Account, on_delete=models.SET_NULL, null=True, blank=True, related_name='+'
|
|
)
|
|
customer_payable_account = models.ForeignKey(
|
|
Account, on_delete=models.SET_NULL, null=True, blank=True, related_name='+'
|
|
)
|
|
owner_share_account = models.ForeignKey(
|
|
Account, on_delete=models.SET_NULL, null=True, blank=True, related_name='+'
|
|
)
|
|
sales_income_account = models.ForeignKey(
|
|
Account, on_delete=models.SET_NULL, null=True, blank=True, related_name='+'
|
|
)
|
|
|
|
class Meta:
|
|
db_table = 'accounting_company_settings'
|
|
|
|
def __str__(self):
|
|
return self.name or 'Company Settings'
|