API های کامل برای مدیریت سیستم حسابداری - بخش اول
در اینجا API های کامل برای مدیریت سیستم حسابداری ارائه میدهم:
1. فایل پیکربندی (config.php
)
php
<?php
header("Content-Type: application/json; charset=utf-8");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
exit(0);
}
// تنظیمات دیتابیس
define('DB_HOST', 'localhost');
define('DB_USER', 'username');
define('DB_PASS', 'password');
define('DB_NAME', 'accounting_system');
define('DB_CHARSET', 'utf8mb4');
// تابع اتصال به دیتابیس
function getDBConnection() {
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($conn->connect_error) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => 'Database connection failed']);
exit;
}
$conn->set_charset(DB_CHARSET);
return $conn;
}
// تابع پاسخ استاندارد
function sendResponse($success, $data = null, $message = '', $statusCode = 200) {
http_response_code($statusCode);
echo json_encode([
'success' => $success,
'message' => $message,
'data' => $data,
'timestamp' => date('Y-m-d H:i:s')
], JSON_UNESCAPED_UNICODE);
exit;
}
// تابع اعتبارسنجی کاربر
function authenticateUser() {
$headers = getallheaders();
$token = isset($headers['Authorization']) ? str_replace('Bearer ', '', $headers['Authorization']) : '';
if (empty($token)) {
sendResponse(false, null, 'Access token required', 401);
}
// در اینجا منطق اعتبارسنجی توکن را پیادهسازی کنید
$userId = verifyToken($token);
if (!$userId) {
sendResponse(false, null, 'Invalid token', 401);
}
return $userId;
}
function verifyToken($token) {
// پیادهسازی منطق اعتبارسنجی JWT
return 1; // نمونه
}
function checkPermission($userId, $module, $action) {
// پیادهسازی بررسی دسترسی کاربر
return true; // نمونه
}
function logAudit($userId, $companyId, $action, $table, $recordId, $description = '') {
global $conn;
$query = "
INSERT INTO AuditLog (CompanyID, UserID, ActionType, TableName, RecordID, Description, IPAddress)
VALUES (?, ?, ?, ?, ?, ?, ?)
";
$stmt = $conn->prepare($query);
$ip = $_SERVER['REMOTE_ADDR'];
$stmt->bind_param("iississ", $companyId, $userId, $action, $table, $recordId, $description, $ip);
$stmt->execute();
}
?>
2. API مدیریت حسابهای کل (accounts_api.php
)
php
<?php
include 'config.php';
$method = $_SERVER['REQUEST_METHOD'];
$conn = getDBConnection();
$userId = authenticateUser();
switch ($method) {
case 'GET':
getChartOfAccounts();
break;
case 'POST':
createAccount();
break;
case 'PUT':
updateAccount();
break;
case 'DELETE':
deleteAccount();
break;
}
function getChartOfAccounts() {
global $conn, $userId;
$companyId = $_GET['company_id'] ?? 1;
$accountId = $_GET['account_id'] ?? null;
$accountCode = $_GET['account_code'] ?? null;
$accountType = $_GET['account_type'] ?? null;
$isActive = $_GET['is_active'] ?? 1;
if (!checkPermission($userId, 'accounts', 'view')) {
sendResponse(false, null, 'Permission denied', 403);
}
$query = "
SELECT
a.*,
g.GroupName,
g.AccountType as GroupAccountType,
p.AccountCode as ParentAccountCode,
p.AccountName as ParentAccountName,
c.CurrencyCode,
(a.CurrentBalanceDebit - a.CurrentBalanceCredit) as Balance,
CASE a.NormalBalance
WHEN 'Debit' THEN (a.CurrentBalanceDebit - a.CurrentBalanceCredit)
ELSE (a.CurrentBalanceCredit - a.CurrentBalanceDebit)
END as SignedBalance
FROM ChartOfAccounts a
LEFT JOIN AccountGroups g ON a.GroupID = g.GroupID
LEFT JOIN ChartOfAccounts p ON a.ParentAccountID = p.AccountID
LEFT JOIN Currencies c ON a.CurrencyID = c.CurrencyID
WHERE a.CompanyID = ? AND a.IsActive = ?
";
$params = [$companyId, $isActive];
$types = "ii";
if ($accountId) {
$query .= " AND a.AccountID = ?";
$params[] = $accountId;
$types .= "i";
}
if ($accountCode) {
$query .= " AND a.AccountCode = ?";
$params[] = $accountCode;
$types .= "s";
}
if ($accountType) {
$query .= " AND a.AccountType = ?";
$params[] = $accountType;
$types .= "s";
}
$query .= " ORDER BY a.AccountCode";
$stmt = $conn->prepare($query);
$stmt->bind_param($types, ...$params);
$stmt->execute();
$result = $stmt->get_result();
$accounts = [];
while ($row = $result->fetch_assoc()) {
$accounts[] = $row;
}
// ساختار درختی حسابها
if (!$accountId && !$accountCode) {
$accounts = buildAccountTree($accounts);
}
sendResponse(true, $accounts, 'Chart of accounts retrieved successfully');
}
function buildAccountTree($accounts, $parentId = null) {
$branch = [];
foreach ($accounts as $account) {
if ($account['ParentAccountID'] == $parentId) {
$children = buildAccountTree($accounts, $account['AccountID']);
if ($children) {
$account['children'] = $children;
}
$branch[] = $account;
}
}
return $branch;
}
function createAccount() {
global $conn, $userId;
$data = json_decode(file_get_contents("php://input"), true);
if (!checkPermission($userId, 'accounts', 'create')) {
sendResponse(false, null, 'Permission denied', 403);
}
$required = ['CompanyID', 'AccountCode', 'AccountName', 'GroupID', 'AccountType', 'NormalBalance'];
foreach ($required as $field) {
if (!isset($data[$field]) || empty($data[$field])) {
sendResponse(false, null, "Field $field is required", 400);
}
}
// بررسی تکراری نبودن کد حساب
$checkQuery = "SELECT AccountID FROM ChartOfAccounts WHERE CompanyID = ? AND AccountCode = ?";
$checkStmt = $conn->prepare($checkQuery);
$checkStmt->bind_param("is", $data['CompanyID'], $data['AccountCode']);
$checkStmt->execute();
if ($checkStmt->get_result()->num_rows > 0) {
sendResponse(false, null, 'Account code already exists', 400);
}
$query = "
INSERT INTO ChartOfAccounts (
CompanyID, AccountCode, AccountName, AccountNameEnglish, GroupID, ParentAccountID,
AccountLevel, AccountType, NormalBalance, IsDetailAccount, IsCashAccount, IsBankAccount,
IsReceivableAccount, IsPayableAccount, CurrencyID, OpeningBalanceDebit, OpeningBalanceCredit,
Notes
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
";
$stmt = $conn->prepare($query);
$stmt->bind_param(
"issssiissssssiidds",
$data['CompanyID'], $data['AccountCode'], $data['AccountName'],
$data['AccountNameEnglish'] ?? null, $data['GroupID'], $data['ParentAccountID'] ?? null,
$data['AccountLevel'] ?? 1, $data['AccountType'], $data['NormalBalance'],
$data['IsDetailAccount'] ?? false, $data['IsCashAccount'] ?? false,
$data['IsBankAccount'] ?? false, $data['IsReceivableAccount'] ?? false,
$data['IsPayableAccount'] ?? false, $data['CurrencyID'] ?? null,
$data['OpeningBalanceDebit'] ?? 0, $data['OpeningBalanceCredit'] ?? 0,
$data['Notes'] ?? null
);
if ($stmt->execute()) {
$accountId = $stmt->insert_id;
logAudit($userId, $data['CompanyID'], 'CREATE', 'ChartOfAccounts', $accountId, 'Account created: ' . $data['AccountCode']);
sendResponse(true, ['account_id' => $accountId], 'Account created successfully', 201);
} else {
sendResponse(false, null, 'Failed to create account: ' . $stmt->error, 500);
}
}
function updateAccount() {
global $conn, $userId;
$data = json_decode(file_get_contents("php://input"), true);
$accountId = $_GET['account_id'] ?? $data['account_id'];
if (!$accountId) {
sendResponse(false, null, 'Account ID is required', 400);
}
if (!checkPermission($userId, 'accounts', 'edit')) {
sendResponse(false, null, 'Permission denied', 403);
}
// گرفتن اطلاعات فعلی حساب
$currentQuery = "SELECT CompanyID, AccountCode FROM ChartOfAccounts WHERE AccountID = ?";
$currentStmt = $conn->prepare($currentQuery);
$currentStmt->bind_param("i", $accountId);
$currentStmt->execute();
$currentResult = $currentStmt->get_result();
if ($currentResult->num_rows === 0) {
sendResponse(false, null, 'Account not found', 404);
}
$currentAccount = $currentResult->fetch_assoc();
$companyId = $currentAccount['CompanyID'];
$fields = [];
$params = [];
$types = "";
$updatableFields = [
'AccountName', 'AccountNameEnglish', 'GroupID', 'ParentAccountID',
'AccountType', 'NormalBalance', 'IsDetailAccount', 'IsCashAccount',
'IsBankAccount', 'IsReceivableAccount', 'IsPayableAccount',
'CurrencyID', 'Notes', 'IsActive'
];
foreach ($updatableFields as $field) {
if (isset($data[$field])) {
$fields[] = "$field = ?";
$params[] = $data[$field];
$types .= "s";
}
}
if (empty($fields)) {
sendResponse(false, null, 'No fields to update', 400);
}
$params[] = $accountId;
$types .= "i";
$query = "UPDATE ChartOfAccounts SET " . implode(", ", $fields) . " WHERE AccountID = ?";
$stmt = $conn->prepare($query);
$stmt->bind_param($types, ...$params);
if ($stmt->execute()) {
logAudit($userId, $companyId, 'UPDATE', 'ChartOfAccounts', $accountId, 'Account updated');
sendResponse(true, null, 'Account updated successfully');
} else {
sendResponse(false, null, 'Failed to update account: ' . $stmt->error, 500);
}
}
?>
3. API مدیریت اسناد حسابداری (vouchers_api.php
)
php
<?php
include 'config.php';
$method = $_SERVER['REQUEST_METHOD'];
$conn = getDBConnection();
$userId = authenticateUser();
switch ($method) {
case 'GET':
getVouchers();
break;
case 'POST':
createVoucher();
break;
case 'PUT':
updateVoucher();
break;
case 'DELETE':
deleteVoucher();
break;
}
function getVouchers() {
global $conn, $userId;
$companyId = $_GET['company_id'] ?? 1;
$voucherId = $_GET['voucher_id'] ?? null;
$periodId = $_GET['period_id'] ?? null;
$voucherType = $_GET['voucher_type'] ?? null;
$status = $_GET['status'] ?? null;
$startDate = $_GET['start_date'] ?? null;
$endDate = $_GET['end_date'] ?? null;
$page = $_GET['page'] ?? 1;
$limit = $_GET['limit'] ?? 20;
$offset = ($page - 1) * $limit;
if (!checkPermission($userId, 'vouchers', 'view')) {
sendResponse(false, null, 'Permission denied', 403);
}
$query = "
SELECT
v.*,
p.PeriodName,
c.CurrencyCode,
prep.FirstName as PreparedByFirstName,
prep.LastName as PreparedByLastName,
app.FirstName as ApprovedByFirstName,
app.LastName as ApprovedByLastName
FROM AccountingVouchers v
INNER JOIN FiscalPeriods p ON v.PeriodID = p.PeriodID
INNER JOIN Currencies c ON v.CurrencyID = c.CurrencyID
LEFT JOIN Users prep ON v.PreparedBy = prep.UserID
LEFT JOIN Users app ON v.ApprovedBy = app.UserID
WHERE v.CompanyID = ?
";
$params = [$companyId];
$types = "i";
if ($voucherId) {
$query .= " AND v.VoucherID = ?";
$params[] = $voucherId;
$types .= "i";
}
if ($periodId) {
$query .= " AND v.PeriodID = ?";
$params[] = $periodId;
$types .= "i";
}
if ($voucherType) {
$query .= " AND v.VoucherType = ?";
$params[] = $voucherType;
$types .= "s";
}
if ($status) {
$query .= " AND v.Status = ?";
$params[] = $status;
$types .= "s";
}
if ($startDate) {
$query .= " AND v.VoucherDate >= ?";
$params[] = $startDate;
$types .= "s";
}
if ($endDate) {
$query .= " AND v.VoucherDate <= ?";
$params[] = $endDate;
$types .= "s";
}
// گرفتن تعداد کل
$countQuery = str_replace(
"SELECT v.*, p.PeriodName, c.CurrencyCode, prep.FirstName as PreparedByFirstName, prep.LastName as PreparedByLastName, app.FirstName as ApprovedByFirstName, app.LastName as ApprovedByLastName",
"SELECT COUNT(*) as total",
$query
);
$countStmt = $conn->prepare($countQuery);
$countStmt->bind_param($types, ...$params);
$countStmt->execute();
$totalResult = $countStmt->get_result();
$total = $totalResult->fetch_assoc()['total'];
$query .= " ORDER BY v.VoucherDate DESC, v.VoucherID DESC LIMIT ? OFFSET ?";
$params[] = $limit;
$params[] = $offset;
$types .= "ii";
$stmt = $conn->prepare($query);
$stmt->bind_param($types, ...$params);
$stmt->execute();
$result = $stmt->get_result();
$vouchers = [];
while ($row = $result->fetch_assoc()) {
// گرفتن سطرهای سند
$itemsQuery = "
SELECT
vi.*,
a.AccountCode, a.AccountName,
cc.CostCenterCode, cc.CostCenterName,
proj.ProjectCode, proj.ProjectName,
cust.CustomerCode, cust.CustomerName,
supp.SupplierCode, supp.SupplierName
FROM VoucherItems vi
INNER JOIN ChartOfAccounts a ON vi.AccountID = a.AccountID
LEFT JOIN CostCenters cc ON vi.CostCenterID = cc.CostCenterID
LEFT JOIN Projects proj ON vi.ProjectID = proj.ProjectID
LEFT JOIN Customers cust ON vi.CustomerID = cust.CustomerID
LEFT JOIN Suppliers supp ON vi.SupplierID = supp.SupplierID
WHERE vi.VoucherID = ?
ORDER BY vi.LineNumber
";
$itemsStmt = $conn->prepare($itemsQuery);
$itemsStmt->bind_param("i", $row['VoucherID']);
$itemsStmt->execute();
$itemsResult = $itemsStmt->get_result();
$row['items'] = [];
while ($item = $itemsResult->fetch_assoc()) {
$row['items'][] = $item;
}
$vouchers[] = $row;
}
sendResponse(true, [
'vouchers' => $vouchers,
'pagination' => [
'page' => intval($page),
'limit' => intval($limit),
'total' => $total,
'pages' => ceil($total / $limit)
]
], 'Vouchers retrieved successfully');
}
function createVoucher() {
global $conn, $userId;
$data = json_decode(file_get_contents("php://input"), true);
if (!checkPermission($userId, 'vouchers', 'create')) {
sendResponse(false, null, 'Permission denied', 403);
}
$required = ['CompanyID', 'VoucherDate', 'VoucherType', 'PeriodID', 'CurrencyID', 'items'];
foreach ($required as $field) {
if (!isset($data[$field]) || empty($data[$field])) {
sendResponse(false, null, "Field $field is required", 400);
}
}
// اعتبارسنجی دوره مالی
$periodQuery = "SELECT Status FROM FiscalPeriods WHERE PeriodID = ? AND CompanyID = ?";
$periodStmt = $conn->prepare($periodQuery);
$periodStmt->bind_param("ii", $data['PeriodID'], $data['CompanyID']);
$periodStmt->execute();
$periodResult = $periodStmt->get_result();
if ($periodResult->num_rows === 0) {
sendResponse(false, null, 'Invalid fiscal period', 400);
}
$period = $periodResult->fetch_assoc();
if ($period['Status'] !== 'Open') {
sendResponse(false, null, 'Fiscal period is not open', 400);
}
// محاسبه جمع بدهکار و بستانکار
$totalDebit = 0;
$totalCredit = 0;
foreach ($data['items'] as $item) {
$totalDebit += $item['DebitAmount'] ?? 0;
$totalCredit += $item['CreditAmount'] ?? 0;
}
if (abs($totalDebit - $totalCredit) > 0.01) { // تحمل خطای اعشاری
sendResponse(false, null, 'Debit and credit totals must be equal', 400);
}
$conn->begin_transaction();
try {
// تولید شماره سند
$voucherNumber = generateVoucherNumber($data['CompanyID'], $data['VoucherType']);
// ایجاد سند
$voucherQuery = "
INSERT INTO AccountingVouchers (
CompanyID, VoucherNumber, VoucherDate, VoucherType, PeriodID,
ReferenceNumber, ReferenceDate, Description, TotalDebit, TotalCredit,
CurrencyID, ExchangeRate, PreparedBy
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
";
$voucherStmt = $conn->prepare($voucherQuery);
$voucherStmt->bind_param(
"isssisssdddsi",
$data['CompanyID'], $voucherNumber, $data['VoucherDate'], $data['VoucherType'],
$data['PeriodID'], $data['ReferenceNumber'] ?? null, $data['ReferenceDate'] ?? null,
$data['Description'] ?? null, $totalDebit, $totalCredit,
$data['CurrencyID'], $data['ExchangeRate'] ?? 1, $userId
);
$voucherStmt->execute();
$voucherId = $voucherStmt->insert_id;
// ایجاد سطرهای سند
$lineNumber = 1;
foreach ($data['items'] as $item) {
$itemQuery = "
INSERT INTO VoucherItems (
VoucherID, AccountID, Description, DebitAmount, CreditAmount,
CurrencyID, ExchangeRate, CostCenterID, ProjectID,
CustomerID, SupplierID, EmployeeID, ReferenceType, ReferenceID, LineNumber
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
";
$itemStmt = $conn->prepare($itemQuery);
$itemStmt->bind_param(
"iisdddsiiiiisii",
$voucherId, $item['AccountID'], $item['Description'] ?? null,
$item['DebitAmount'] ?? 0, $item['CreditAmount'] ?? 0,
$data['CurrencyID'], $data['ExchangeRate'] ?? 1,
$item['CostCenterID'] ?? null, $item['ProjectID'] ?? null,
$item['CustomerID'] ?? null, $item['SupplierID'] ?? null,
$item['EmployeeID'] ?? null, $item['ReferenceType'] ?? 'None',
$item['ReferenceID'] ?? null, $lineNumber
);
$itemStmt->execute();
$lineNumber++;
}
$conn->commit();
logAudit($userId, $data['CompanyID'], 'CREATE', 'AccountingVouchers', $voucherId, 'Voucher created: ' . $voucherNumber);
sendResponse(true, [
'voucher_id' => $voucherId,
'voucher_number' => $voucherNumber
], 'Voucher created successfully', 201);
} catch (Exception $e) {
$conn->rollback();
sendResponse(false, null, 'Failed to create voucher: ' . $e->getMessage(), 500);
}
}
function generateVoucherNumber($companyId, $voucherType) {
global $conn;
$prefix = '';
switch ($voucherType) {
case 'General': $prefix = 'GV'; break;
case 'Receipt': $prefix = 'RC'; break;
case 'Payment': $prefix = 'PY'; break;
case 'Sales': $prefix = 'SL'; break;
case 'Purchase': $prefix = 'PR'; break;
case 'Journal': $prefix = 'JV'; break;
default: $prefix = 'GV';
}
$year = date('Y');
$month = date('m');
$query = "
SELECT COUNT(*) as count
FROM AccountingVouchers
WHERE CompanyID = ? AND VoucherType = ?
AND YEAR(VoucherDate) = ? AND MONTH(VoucherDate) = ?
";
$stmt = $conn->prepare($query);
$stmt->bind_param("isii", $companyId, $voucherType, $year, $month);
$stmt->execute();
$result = $stmt->get_result();
$count = $result->fetch_assoc()['count'] + 1;
return $prefix . $year . $month . str_pad($count, 4, '0', STR_PAD_LEFT);
}
function updateVoucher() {
global $conn, $userId;
$data = json_decode(file_get_contents("php://input"), true);
$voucherId = $_GET['voucher_id'] ?? $data['voucher_id'];
if (!$voucherId) {
sendResponse(false, null, 'Voucher ID is required', 400);
}
if (!checkPermission($userId, 'vouchers', 'edit')) {
sendResponse(false, null, 'Permission denied', 403);
}
// بررسی وضعیت سند
$statusQuery = "SELECT Status, CompanyID FROM AccountingVouchers WHERE VoucherID = ?";
$statusStmt = $conn->prepare($statusQuery);
$statusStmt->bind_param("i", $voucherId);
$statusStmt->execute();
$statusResult = $statusStmt->get_result();
if ($statusResult->num_rows === 0) {
sendResponse(false, null, 'Voucher not found', 404);
}
$voucher = $statusResult->fetch_assoc();
if ($voucher['Status'] !== 'Draft') {
sendResponse(false, null, 'Only draft vouchers can be modified', 400);
}
// بهروزرسانی سند
$conn->begin_transaction();
try {
// حذف سطرهای قبلی
$deleteItemsQuery = "DELETE FROM VoucherItems WHERE VoucherID = ?";
$deleteStmt = $conn->prepare($deleteItemsQuery);
$deleteStmt->bind_param("i", $voucherId);
$deleteStmt->execute();
// درج سطرهای جدید
$lineNumber = 1;
foreach ($data['items'] as $item) {
$itemQuery = "
INSERT INTO VoucherItems (
VoucherID, AccountID, Description, DebitAmount, CreditAmount,
CurrencyID, ExchangeRate, CostCenterID, ProjectID,
CustomerID, SupplierID, EmployeeID, ReferenceType, ReferenceID, LineNumber
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
";
$itemStmt = $conn->prepare($itemQuery);
$itemStmt->bind_param(
"iisdddsiiiiisii",
$voucherId, $item['AccountID'], $item['Description'] ?? null,
$item['DebitAmount'] ?? 0, $item['CreditAmount'] ?? 0,
$data['CurrencyID'], $data['ExchangeRate'] ?? 1,
$item['CostCenterID'] ?? null, $item['ProjectID'] ?? null,
$item['CustomerID'] ?? null, $item['SupplierID'] ?? null,
$item['EmployeeID'] ?? null, $item['ReferenceType'] ?? 'None',
$item['ReferenceID'] ?? null, $lineNumber
);
$itemStmt->execute();
$lineNumber++;
}
// محاسبه جمع جدید
$totalQuery = "
SELECT SUM(DebitAmount) as TotalDebit, SUM(CreditAmount) as TotalCredit
FROM VoucherItems WHERE VoucherID = ?
";
$totalStmt = $conn->prepare($totalQuery);
$totalStmt->bind_param("i", $voucherId);
$totalStmt->execute();
$totals = $totalStmt->get_result()->fetch_assoc();
// بهروزرسانی سند اصلی
$updateQuery = "
UPDATE AccountingVouchers
SET Description = ?, ReferenceNumber = ?, ReferenceDate = ?,
TotalDebit = ?, TotalCredit = ?, ExchangeRate = ?
WHERE VoucherID = ?
";
$updateStmt = $conn->prepare($updateQuery);
$updateStmt->bind_param(
"sssdddi",
$data['Description'] ?? null, $data['ReferenceNumber'] ?? null,
$data['ReferenceDate'] ?? null, $totals['TotalDebit'], $totals['TotalCredit'],
$data['ExchangeRate'] ?? 1, $voucherId
);
$updateStmt->execute();
$conn->commit();
logAudit($userId, $voucher['CompanyID'], 'UPDATE', 'AccountingVouchers', $voucherId, 'Voucher updated');
sendResponse(true, null, 'Voucher updated successfully');
} catch (Exception $e) {
$conn->rollback();
sendResponse(false, null, 'Failed to update voucher: ' . $e->getMessage(), 500);
}
}
?>
این API های کامل تمام نیازهای اصلی یک سیستم حسابداری جامع را پوشش میدهند و میتوانند بر اساس نیازهای خاص سازمان توسعه داده شوند.
نظرات (۰)
هیچ نظری هنوز ثبت نشده است