Skip to content

Commit a4c1ef4

Browse files
committed
feat(phase-9): add Finance, Accounting, Purchase API controllers (partial)
Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8f317da commit a4c1ef4

3 files changed

Lines changed: 386 additions & 0 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api\V1;
4+
5+
use App\Modules\Accounting\Models\Account;
6+
use App\Modules\Accounting\Models\JournalEntry;
7+
use Illuminate\Http\JsonResponse;
8+
use Illuminate\Http\Request;
9+
10+
class AccountingApiController extends ApiController
11+
{
12+
/**
13+
* GET /api/v1/accounting/journal-entries
14+
*/
15+
public function journalEntries(Request $request): JsonResponse
16+
{
17+
$query = JournalEntry::withCount('lines');
18+
19+
if ($status = $request->query('status')) {
20+
$query->where('status', $status);
21+
}
22+
23+
if ($dateFrom = $request->query('date_from')) {
24+
$query->whereDate('entry_date', '>=', $dateFrom);
25+
}
26+
27+
if ($dateTo = $request->query('date_to')) {
28+
$query->whereDate('entry_date', '<=', $dateTo);
29+
}
30+
31+
$paginator = $query->latest('entry_date')->paginate(20);
32+
33+
return $this->paginated($paginator);
34+
}
35+
36+
/**
37+
* GET /api/v1/accounting/journal-entries/{id}
38+
*/
39+
public function showJournalEntry(int $id): JsonResponse
40+
{
41+
$entry = JournalEntry::with('lines')->findOrFail($id);
42+
43+
return $this->success($entry);
44+
}
45+
46+
/**
47+
* POST /api/v1/accounting/journal-entries
48+
*/
49+
public function storeJournalEntry(Request $request): JsonResponse
50+
{
51+
$validated = $request->validate([
52+
'reference' => 'nullable|string|max:255',
53+
'description' => 'required|string',
54+
'entry_date' => 'required|date',
55+
]);
56+
57+
$tenantId = app()->has('tenant') ? app('tenant')->id : $request->user()->tenant_id;
58+
59+
$validated['tenant_id'] = $tenantId;
60+
$validated['status'] = 'draft';
61+
62+
$entry = JournalEntry::create($validated);
63+
64+
return $this->success($entry, 201);
65+
}
66+
67+
/**
68+
* GET /api/v1/accounting/accounts
69+
*/
70+
public function accounts(Request $request): JsonResponse
71+
{
72+
$query = Account::select('id', 'code', 'name', 'type', 'normal_balance', 'is_active');
73+
74+
if ($type = $request->query('type')) {
75+
$query->where('type', $type);
76+
}
77+
78+
if ($request->has('is_active')) {
79+
$query->where('is_active', filter_var($request->query('is_active'), FILTER_VALIDATE_BOOLEAN));
80+
}
81+
82+
$paginator = $query->orderBy('code')->paginate(20);
83+
84+
return $this->paginated($paginator);
85+
}
86+
87+
/**
88+
* POST /api/v1/accounting/accounts
89+
*/
90+
public function storeAccount(Request $request): JsonResponse
91+
{
92+
$validated = $request->validate([
93+
'code' => 'required|string|max:50',
94+
'name' => 'required|string|max:255',
95+
'type' => 'required|string|in:asset,liability,equity,revenue,expense',
96+
'normal_balance' => 'required|string|in:debit,credit',
97+
'sub_type' => 'nullable|string|max:100',
98+
'is_active' => 'nullable|boolean',
99+
]);
100+
101+
$tenantId = app()->has('tenant') ? app('tenant')->id : $request->user()->tenant_id;
102+
103+
$validated['tenant_id'] = $tenantId;
104+
105+
$account = Account::create($validated);
106+
107+
return $this->success($account, 201);
108+
}
109+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api\V1;
4+
5+
use App\Modules\Finance\Models\Bill;
6+
use App\Modules\Finance\Models\Contact;
7+
use Illuminate\Http\JsonResponse;
8+
use Illuminate\Http\Request;
9+
10+
class FinanceApiController extends ApiController
11+
{
12+
/**
13+
* GET /api/v1/finance/bills
14+
*/
15+
public function index(Request $request): JsonResponse
16+
{
17+
$query = Bill::with('contact:id,name');
18+
19+
if ($status = $request->query('status')) {
20+
$query->where('status', $status);
21+
}
22+
23+
if ($contactId = $request->query('contact_id')) {
24+
$query->where('contact_id', $contactId);
25+
}
26+
27+
$paginator = $query->latest()->paginate(20);
28+
29+
return $this->paginated($paginator);
30+
}
31+
32+
/**
33+
* GET /api/v1/finance/bills/{id}
34+
*/
35+
public function show(int $id): JsonResponse
36+
{
37+
$bill = Bill::with(['items', 'contact'])->findOrFail($id);
38+
39+
return $this->success($bill);
40+
}
41+
42+
/**
43+
* POST /api/v1/finance/bills
44+
*/
45+
public function store(Request $request): JsonResponse
46+
{
47+
$validated = $request->validate([
48+
'contact_id' => 'required|integer|exists:contacts,id',
49+
'bill_date' => 'required|date',
50+
'due_date' => 'nullable|date',
51+
'notes' => 'nullable|string',
52+
]);
53+
54+
$tenantId = app()->has('tenant') ? app('tenant')->id : $request->user()->tenant_id;
55+
56+
$validated['tenant_id'] = $tenantId;
57+
58+
$bill = Bill::create($validated);
59+
60+
return $this->success($bill, 201);
61+
}
62+
63+
/**
64+
* PUT /api/v1/finance/bills/{id}
65+
*/
66+
public function update(Request $request, int $id): JsonResponse
67+
{
68+
$bill = Bill::findOrFail($id);
69+
70+
$validated = $request->validate([
71+
'contact_id' => 'nullable|integer|exists:contacts,id',
72+
'bill_date' => 'nullable|date',
73+
'due_date' => 'nullable|date',
74+
'notes' => 'nullable|string',
75+
]);
76+
77+
$bill->update($validated);
78+
79+
return $this->success($bill);
80+
}
81+
82+
/**
83+
* DELETE /api/v1/finance/bills/{id}
84+
*/
85+
public function destroy(int $id): JsonResponse
86+
{
87+
$bill = Bill::findOrFail($id);
88+
89+
if ($bill->status === 'paid') {
90+
return $this->error('Cannot delete a paid bill.', 422);
91+
}
92+
93+
$bill->delete();
94+
95+
return $this->success(['message' => 'Bill deleted']);
96+
}
97+
98+
/**
99+
* GET /api/v1/finance/contacts
100+
*/
101+
public function contacts(Request $request): JsonResponse
102+
{
103+
$query = Contact::query();
104+
105+
if ($type = $request->query('type')) {
106+
$query->where('type', $type);
107+
}
108+
109+
$paginator = $query->latest()->paginate(20);
110+
111+
return $this->paginated($paginator);
112+
}
113+
114+
/**
115+
* POST /api/v1/finance/contacts
116+
*/
117+
public function storeContact(Request $request): JsonResponse
118+
{
119+
$validated = $request->validate([
120+
'name' => 'required|string|max:255',
121+
'email' => 'nullable|email|max:255',
122+
'phone' => 'nullable|string|max:50',
123+
'type' => 'nullable|string|in:customer,supplier,both',
124+
]);
125+
126+
$tenantId = app()->has('tenant') ? app('tenant')->id : $request->user()->tenant_id;
127+
128+
$validated['tenant_id'] = $tenantId;
129+
130+
$contact = Contact::create($validated);
131+
132+
return $this->success($contact, 201);
133+
}
134+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
namespace App\Http\Controllers\Api\V1;
4+
5+
use App\Modules\Purchase\Models\Po;
6+
use App\Modules\Purchase\Models\PurchaseRfq;
7+
use App\Modules\Purchase\Models\PurchaseVendor;
8+
use Illuminate\Http\JsonResponse;
9+
use Illuminate\Http\Request;
10+
11+
class PurchaseApiController extends ApiController
12+
{
13+
/**
14+
* GET /api/v1/purchase/vendors
15+
*/
16+
public function vendors(Request $request): JsonResponse
17+
{
18+
$query = PurchaseVendor::select('id', 'name', 'email', 'currency', 'is_active');
19+
20+
if ($request->has('is_active')) {
21+
$query->where('is_active', filter_var($request->query('is_active'), FILTER_VALIDATE_BOOLEAN));
22+
}
23+
24+
$paginator = $query->latest()->paginate(20);
25+
26+
return $this->paginated($paginator);
27+
}
28+
29+
/**
30+
* POST /api/v1/purchase/vendors
31+
*/
32+
public function storeVendor(Request $request): JsonResponse
33+
{
34+
$validated = $request->validate([
35+
'name' => 'required|string|max:255',
36+
'email' => 'nullable|email|max:255',
37+
'phone' => 'nullable|string|max:50',
38+
'currency' => 'nullable|string|max:10',
39+
'is_active' => 'nullable|boolean',
40+
]);
41+
42+
$tenantId = app()->has('tenant') ? app('tenant')->id : $request->user()->tenant_id;
43+
44+
$validated['tenant_id'] = $tenantId;
45+
46+
$vendor = PurchaseVendor::create($validated);
47+
48+
return $this->success($vendor, 201);
49+
}
50+
51+
/**
52+
* GET /api/v1/purchase/rfqs
53+
*/
54+
public function rfqs(Request $request): JsonResponse
55+
{
56+
$query = PurchaseRfq::with('vendor:id,name');
57+
58+
if ($status = $request->query('status')) {
59+
$query->where('status', $status);
60+
}
61+
62+
if ($vendorId = $request->query('po_vendor_id')) {
63+
$query->where('po_vendor_id', $vendorId);
64+
}
65+
66+
$paginator = $query->latest()->paginate(20);
67+
68+
return $this->paginated($paginator);
69+
}
70+
71+
/**
72+
* POST /api/v1/purchase/rfqs
73+
*/
74+
public function storeRfq(Request $request): JsonResponse
75+
{
76+
$validated = $request->validate([
77+
'po_vendor_id' => 'required|integer|exists:po_vendors,id',
78+
'expected_delivery' => 'nullable|date',
79+
'notes' => 'nullable|string',
80+
'currency' => 'nullable|string|max:10',
81+
]);
82+
83+
$tenantId = app()->has('tenant') ? app('tenant')->id : $request->user()->tenant_id;
84+
85+
$validated['tenant_id'] = $tenantId;
86+
87+
$rfq = PurchaseRfq::create($validated);
88+
89+
return $this->success($rfq, 201);
90+
}
91+
92+
/**
93+
* GET /api/v1/purchase/purchase-orders
94+
*/
95+
public function purchaseOrders(Request $request): JsonResponse
96+
{
97+
$query = Po::with('vendor:id,name')->withCount('lines');
98+
99+
if ($status = $request->query('status')) {
100+
$query->where('status', $status);
101+
}
102+
103+
if ($vendorId = $request->query('po_vendor_id')) {
104+
$query->where('po_vendor_id', $vendorId);
105+
}
106+
107+
$paginator = $query->latest()->paginate(20);
108+
109+
return $this->paginated($paginator);
110+
}
111+
112+
/**
113+
* GET /api/v1/purchase/purchase-orders/{id}
114+
*/
115+
public function showPurchaseOrder(int $id): JsonResponse
116+
{
117+
$po = Po::with(['vendor', 'lines'])->findOrFail($id);
118+
119+
return $this->success($po);
120+
}
121+
122+
/**
123+
* POST /api/v1/purchase/purchase-orders/{id}/confirm
124+
*/
125+
public function confirmPurchaseOrder(int $id): JsonResponse
126+
{
127+
$po = Po::findOrFail($id);
128+
$po->confirm();
129+
130+
return $this->success(['message' => 'Purchase order confirmed']);
131+
}
132+
133+
/**
134+
* POST /api/v1/purchase/purchase-orders/{id}/receive
135+
*/
136+
public function receivePurchaseOrder(int $id): JsonResponse
137+
{
138+
$po = Po::findOrFail($id);
139+
$po->receive();
140+
141+
return $this->success(['message' => 'Purchase order received']);
142+
}
143+
}

0 commit comments

Comments
 (0)