Phase 5 판매대장/일계표/기간별현황/수불현황 리포트 구현
- SalesReport 컨트롤러 (판매대장, 일계표, 기간별판매, 수불현황) - 판매대장: 일자별/기간별 모드 전환, 판매소별 품목 집계 - 일계표: 당일 판매 + 당월 누계 병렬 표시 - 기간별 판매현황: 품목별 판매/반품/합계 + 총합계 - 봉투 수불현황: 현재재고/입고/판매/불출 4섹션 그리드 레이아웃 - E2E 테스트 6개 전체 통과 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -123,6 +123,12 @@ $routes->group('admin', ['filter' => 'adminAuth'], static function ($routes): vo
|
|||||||
$routes->post('packaging-units/delete/(:num)', 'Admin\PackagingUnit::delete/$1');
|
$routes->post('packaging-units/delete/(:num)', 'Admin\PackagingUnit::delete/$1');
|
||||||
$routes->get('packaging-units/history/(:num)', 'Admin\PackagingUnit::history/$1');
|
$routes->get('packaging-units/history/(:num)', 'Admin\PackagingUnit::history/$1');
|
||||||
|
|
||||||
|
// 현황/리포트 (Phase 5)
|
||||||
|
$routes->get('reports/sales-ledger', 'Admin\SalesReport::salesLedger');
|
||||||
|
$routes->get('reports/daily-summary', 'Admin\SalesReport::dailySummary');
|
||||||
|
$routes->get('reports/period-sales', 'Admin\SalesReport::periodSales');
|
||||||
|
$routes->get('reports/supply-demand', 'Admin\SalesReport::supplyDemand');
|
||||||
|
|
||||||
// 판매 대행소 관리 (P2-07/08)
|
// 판매 대행소 관리 (P2-07/08)
|
||||||
$routes->get('sales-agencies', 'Admin\SalesAgency::index');
|
$routes->get('sales-agencies', 'Admin\SalesAgency::index');
|
||||||
$routes->get('sales-agencies/create', 'Admin\SalesAgency::create');
|
$routes->get('sales-agencies/create', 'Admin\SalesAgency::create');
|
||||||
|
|||||||
180
app/Controllers/Admin/SalesReport.php
Normal file
180
app/Controllers/Admin/SalesReport.php
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Controllers\BaseController;
|
||||||
|
use App\Models\BagSaleModel;
|
||||||
|
use App\Models\BagIssueModel;
|
||||||
|
use App\Models\BagReceivingModel;
|
||||||
|
use App\Models\BagInventoryModel;
|
||||||
|
|
||||||
|
class SalesReport extends BaseController
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* P5-01: 판매 대장 (일자별/기간별)
|
||||||
|
*/
|
||||||
|
public function salesLedger()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||||
|
|
||||||
|
$startDate = $this->request->getGet('start_date') ?? date('Y-m-01');
|
||||||
|
$endDate = $this->request->getGet('end_date') ?? date('Y-m-d');
|
||||||
|
$mode = $this->request->getGet('mode') ?? 'daily'; // daily or period
|
||||||
|
|
||||||
|
$saleModel = model(BagSaleModel::class);
|
||||||
|
$db = \Config\Database::connect();
|
||||||
|
|
||||||
|
if ($mode === 'daily') {
|
||||||
|
$result = $db->query("
|
||||||
|
SELECT bs_sale_date, bs_ds_name, bs_bag_code, bs_bag_name, bs_type,
|
||||||
|
SUM(ABS(bs_qty)) as total_qty,
|
||||||
|
SUM(bs_amount) as total_amount
|
||||||
|
FROM bag_sale
|
||||||
|
WHERE bs_lg_idx = ? AND bs_sale_date BETWEEN ? AND ? AND bs_type IN('sale','return')
|
||||||
|
GROUP BY bs_sale_date, bs_ds_name, bs_bag_code, bs_bag_name, bs_type
|
||||||
|
ORDER BY bs_sale_date DESC, bs_ds_name, bs_bag_code
|
||||||
|
", [$lgIdx, $startDate, $endDate])->getResult();
|
||||||
|
} else {
|
||||||
|
$result = $db->query("
|
||||||
|
SELECT bs_ds_name, bs_bag_code, bs_bag_name,
|
||||||
|
SUM(CASE WHEN bs_type='sale' THEN ABS(bs_qty) ELSE 0 END) as sale_qty,
|
||||||
|
SUM(CASE WHEN bs_type='sale' THEN bs_amount ELSE 0 END) as sale_amount,
|
||||||
|
SUM(CASE WHEN bs_type='return' THEN ABS(bs_qty) ELSE 0 END) as return_qty,
|
||||||
|
SUM(CASE WHEN bs_type='return' THEN bs_amount ELSE 0 END) as return_amount
|
||||||
|
FROM bag_sale
|
||||||
|
WHERE bs_lg_idx = ? AND bs_sale_date BETWEEN ? AND ?
|
||||||
|
GROUP BY bs_ds_name, bs_bag_code, bs_bag_name
|
||||||
|
ORDER BY bs_ds_name, bs_bag_code
|
||||||
|
", [$lgIdx, $startDate, $endDate])->getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('admin/layout', [
|
||||||
|
'title' => '판매 대장',
|
||||||
|
'content' => view('admin/sales_report/sales_ledger', compact('result', 'startDate', 'endDate', 'mode')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* P5-02: 일계표
|
||||||
|
*/
|
||||||
|
public function dailySummary()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||||
|
|
||||||
|
$date = $this->request->getGet('date') ?? date('Y-m-d');
|
||||||
|
$db = \Config\Database::connect();
|
||||||
|
|
||||||
|
// 당일 판매
|
||||||
|
$daily = $db->query("
|
||||||
|
SELECT bs_bag_code, bs_bag_name,
|
||||||
|
SUM(CASE WHEN bs_type='sale' THEN ABS(bs_qty) ELSE 0 END) as sale_qty,
|
||||||
|
SUM(CASE WHEN bs_type='sale' THEN bs_amount ELSE 0 END) as sale_amount
|
||||||
|
FROM bag_sale
|
||||||
|
WHERE bs_lg_idx = ? AND bs_sale_date = ?
|
||||||
|
GROUP BY bs_bag_code, bs_bag_name
|
||||||
|
ORDER BY bs_bag_code
|
||||||
|
", [$lgIdx, $date])->getResult();
|
||||||
|
|
||||||
|
// 당월 누계
|
||||||
|
$monthStart = date('Y-m-01', strtotime($date));
|
||||||
|
$monthly = $db->query("
|
||||||
|
SELECT bs_bag_code, bs_bag_name,
|
||||||
|
SUM(CASE WHEN bs_type='sale' THEN ABS(bs_qty) ELSE 0 END) as sale_qty,
|
||||||
|
SUM(CASE WHEN bs_type='sale' THEN bs_amount ELSE 0 END) as sale_amount
|
||||||
|
FROM bag_sale
|
||||||
|
WHERE bs_lg_idx = ? AND bs_sale_date BETWEEN ? AND ?
|
||||||
|
GROUP BY bs_bag_code, bs_bag_name
|
||||||
|
ORDER BY bs_bag_code
|
||||||
|
", [$lgIdx, $monthStart, $date])->getResult();
|
||||||
|
|
||||||
|
return view('admin/layout', [
|
||||||
|
'title' => '일계표',
|
||||||
|
'content' => view('admin/sales_report/daily_summary', compact('daily', 'monthly', 'date')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* P5-03: 기간별 판매현황
|
||||||
|
*/
|
||||||
|
public function periodSales()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||||
|
|
||||||
|
$startDate = $this->request->getGet('start_date') ?? date('Y-m-01');
|
||||||
|
$endDate = $this->request->getGet('end_date') ?? date('Y-m-d');
|
||||||
|
$db = \Config\Database::connect();
|
||||||
|
|
||||||
|
$result = $db->query("
|
||||||
|
SELECT bs_bag_code, bs_bag_name,
|
||||||
|
SUM(CASE WHEN bs_type='sale' THEN ABS(bs_qty) ELSE 0 END) as sale_qty,
|
||||||
|
SUM(CASE WHEN bs_type='sale' THEN bs_amount ELSE 0 END) as sale_amount,
|
||||||
|
SUM(CASE WHEN bs_type='return' THEN ABS(bs_qty) ELSE 0 END) as return_qty,
|
||||||
|
SUM(CASE WHEN bs_type='return' THEN bs_amount ELSE 0 END) as return_amount
|
||||||
|
FROM bag_sale
|
||||||
|
WHERE bs_lg_idx = ? AND bs_sale_date BETWEEN ? AND ?
|
||||||
|
GROUP BY bs_bag_code, bs_bag_name
|
||||||
|
ORDER BY bs_bag_code
|
||||||
|
", [$lgIdx, $startDate, $endDate])->getResult();
|
||||||
|
|
||||||
|
return view('admin/layout', [
|
||||||
|
'title' => '기간별 판매현황',
|
||||||
|
'content' => view('admin/sales_report/period_sales', compact('result', 'startDate', 'endDate')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* P5-07: 봉투 수불 현황
|
||||||
|
*/
|
||||||
|
public function supplyDemand()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||||
|
|
||||||
|
$startDate = $this->request->getGet('start_date') ?? date('Y-m-01');
|
||||||
|
$endDate = $this->request->getGet('end_date') ?? date('Y-m-d');
|
||||||
|
$db = \Config\Database::connect();
|
||||||
|
|
||||||
|
// 입고 합계
|
||||||
|
$receiving = $db->query("
|
||||||
|
SELECT br_bag_code, br_bag_name,
|
||||||
|
SUM(br_qty_sheet) as recv_qty
|
||||||
|
FROM bag_receiving
|
||||||
|
WHERE br_lg_idx = ? AND br_receive_date BETWEEN ? AND ?
|
||||||
|
GROUP BY br_bag_code, br_bag_name
|
||||||
|
", [$lgIdx, $startDate, $endDate])->getResult();
|
||||||
|
|
||||||
|
// 판매 합계
|
||||||
|
$sales = $db->query("
|
||||||
|
SELECT bs_bag_code, bs_bag_name,
|
||||||
|
SUM(CASE WHEN bs_type='sale' THEN ABS(bs_qty) ELSE 0 END) as sale_qty,
|
||||||
|
SUM(CASE WHEN bs_type='return' THEN ABS(bs_qty) ELSE 0 END) as return_qty
|
||||||
|
FROM bag_sale
|
||||||
|
WHERE bs_lg_idx = ? AND bs_sale_date BETWEEN ? AND ?
|
||||||
|
GROUP BY bs_bag_code, bs_bag_name
|
||||||
|
", [$lgIdx, $startDate, $endDate])->getResult();
|
||||||
|
|
||||||
|
// 불출 합계
|
||||||
|
$issues = $db->query("
|
||||||
|
SELECT bi2_bag_code, bi2_bag_name,
|
||||||
|
SUM(bi2_qty) as issue_qty
|
||||||
|
FROM bag_issue
|
||||||
|
WHERE bi2_lg_idx = ? AND bi2_issue_date BETWEEN ? AND ? AND bi2_status = 'normal'
|
||||||
|
GROUP BY bi2_bag_code, bi2_bag_name
|
||||||
|
", [$lgIdx, $startDate, $endDate])->getResult();
|
||||||
|
|
||||||
|
// 현재 재고
|
||||||
|
$inventory = model(BagInventoryModel::class)->where('bi_lg_idx', $lgIdx)->findAll();
|
||||||
|
|
||||||
|
return view('admin/layout', [
|
||||||
|
'title' => '봉투 수불 현황',
|
||||||
|
'content' => view('admin/sales_report/supply_demand', compact('receiving', 'sales', 'issues', 'inventory', 'startDate', 'endDate')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
102
app/Views/admin/sales_report/daily_summary.php
Normal file
102
app/Views/admin/sales_report/daily_summary.php
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||||
|
<span class="text-sm font-bold text-gray-700">일계표</span>
|
||||||
|
</section>
|
||||||
|
<section class="p-2 bg-white border-b border-gray-200">
|
||||||
|
<form method="GET" action="<?= base_url('admin/reports/daily-summary') ?>" class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="text-sm text-gray-600">조회일</label>
|
||||||
|
<input type="date" name="date" value="<?= esc($date ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||||
|
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="flex gap-4 mt-2">
|
||||||
|
<!-- 당일 -->
|
||||||
|
<div class="flex-1 border border-gray-300 overflow-auto">
|
||||||
|
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
|
||||||
|
<span class="text-sm font-bold text-gray-700">당일 (<?= esc($date ?? '') ?>)</span>
|
||||||
|
</div>
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>봉투코드</th>
|
||||||
|
<th>봉투명</th>
|
||||||
|
<th>판매수량</th>
|
||||||
|
<th>판매금액</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php
|
||||||
|
$dailySaleQtyTotal = 0;
|
||||||
|
$dailySaleAmountTotal = 0;
|
||||||
|
?>
|
||||||
|
<?php foreach ($daily as $row): ?>
|
||||||
|
<?php
|
||||||
|
$dailySaleQtyTotal += (int) $row->sale_qty;
|
||||||
|
$dailySaleAmountTotal += (int) $row->sale_amount;
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center font-mono"><?= esc($row->bs_bag_code) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bs_bag_name) ?></td>
|
||||||
|
<td><?= number_format((int) $row->sale_qty) ?></td>
|
||||||
|
<td><?= number_format((int) $row->sale_amount) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($daily)): ?>
|
||||||
|
<tr><td colspan="4" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot class="bg-gray-50 font-bold text-right">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="text-center">합계</td>
|
||||||
|
<td><?= number_format($dailySaleQtyTotal) ?></td>
|
||||||
|
<td><?= number_format($dailySaleAmountTotal) ?></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 당월 누계 -->
|
||||||
|
<div class="flex-1 border border-gray-300 overflow-auto">
|
||||||
|
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
|
||||||
|
<span class="text-sm font-bold text-gray-700">당월 누계 (<?= esc($monthStart ?? '') ?> ~ <?= esc($date ?? '') ?>)</span>
|
||||||
|
</div>
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>봉투코드</th>
|
||||||
|
<th>봉투명</th>
|
||||||
|
<th>판매수량</th>
|
||||||
|
<th>판매금액</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php
|
||||||
|
$monthlySaleQtyTotal = 0;
|
||||||
|
$monthlySaleAmountTotal = 0;
|
||||||
|
?>
|
||||||
|
<?php foreach ($monthly as $row): ?>
|
||||||
|
<?php
|
||||||
|
$monthlySaleQtyTotal += (int) $row->sale_qty;
|
||||||
|
$monthlySaleAmountTotal += (int) $row->sale_amount;
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center font-mono"><?= esc($row->bs_bag_code) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bs_bag_name) ?></td>
|
||||||
|
<td><?= number_format((int) $row->sale_qty) ?></td>
|
||||||
|
<td><?= number_format((int) $row->sale_amount) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($monthly)): ?>
|
||||||
|
<tr><td colspan="4" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot class="bg-gray-50 font-bold text-right">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="text-center">합계</td>
|
||||||
|
<td><?= number_format($monthlySaleQtyTotal) ?></td>
|
||||||
|
<td><?= number_format($monthlySaleAmountTotal) ?></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
69
app/Views/admin/sales_report/period_sales.php
Normal file
69
app/Views/admin/sales_report/period_sales.php
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||||
|
<span class="text-sm font-bold text-gray-700">기간별 판매현황</span>
|
||||||
|
</section>
|
||||||
|
<section class="p-2 bg-white border-b border-gray-200">
|
||||||
|
<form method="GET" action="<?= base_url('admin/reports/period-sales') ?>" class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="text-sm text-gray-600">시작일</label>
|
||||||
|
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||||
|
<label class="text-sm text-gray-600">~</label>
|
||||||
|
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||||
|
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="border border-gray-300 overflow-auto mt-2">
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>봉투코드</th>
|
||||||
|
<th>봉투명</th>
|
||||||
|
<th>판매수량</th>
|
||||||
|
<th>판매금액</th>
|
||||||
|
<th>반품수량</th>
|
||||||
|
<th>반품금액</th>
|
||||||
|
<th>합계수량</th>
|
||||||
|
<th>합계금액</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php
|
||||||
|
$grandSaleQty = 0;
|
||||||
|
$grandSaleAmount = 0;
|
||||||
|
$grandReturnQty = 0;
|
||||||
|
$grandReturnAmount = 0;
|
||||||
|
?>
|
||||||
|
<?php foreach ($result as $row): ?>
|
||||||
|
<?php
|
||||||
|
$grandSaleQty += (int) $row->sale_qty;
|
||||||
|
$grandSaleAmount += (int) $row->sale_amount;
|
||||||
|
$grandReturnQty += (int) $row->return_qty;
|
||||||
|
$grandReturnAmount += (int) $row->return_amount;
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center font-mono"><?= esc($row->bs_bag_code) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bs_bag_name) ?></td>
|
||||||
|
<td><?= number_format((int) $row->sale_qty) ?></td>
|
||||||
|
<td><?= number_format((int) $row->sale_amount) ?></td>
|
||||||
|
<td><?= number_format((int) $row->return_qty) ?></td>
|
||||||
|
<td><?= number_format((int) $row->return_amount) ?></td>
|
||||||
|
<td class="font-bold"><?= number_format((int) $row->sale_qty - (int) $row->return_qty) ?></td>
|
||||||
|
<td class="font-bold"><?= number_format((int) $row->sale_amount - (int) $row->return_amount) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($result)): ?>
|
||||||
|
<tr><td colspan="8" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot class="bg-gray-50 font-bold text-right">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="text-center">합계</td>
|
||||||
|
<td><?= number_format($grandSaleQty) ?></td>
|
||||||
|
<td><?= number_format($grandSaleAmount) ?></td>
|
||||||
|
<td><?= number_format($grandReturnQty) ?></td>
|
||||||
|
<td><?= number_format($grandReturnAmount) ?></td>
|
||||||
|
<td><?= number_format($grandSaleQty - $grandReturnQty) ?></td>
|
||||||
|
<td><?= number_format($grandSaleAmount - $grandReturnAmount) ?></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
93
app/Views/admin/sales_report/sales_ledger.php
Normal file
93
app/Views/admin/sales_report/sales_ledger.php
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||||
|
<span class="text-sm font-bold text-gray-700">판매 대장</span>
|
||||||
|
</section>
|
||||||
|
<section class="p-2 bg-white border-b border-gray-200">
|
||||||
|
<form method="GET" action="<?= base_url('admin/reports/sales-ledger') ?>" class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="text-sm text-gray-600">시작일</label>
|
||||||
|
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||||
|
<label class="text-sm text-gray-600">~</label>
|
||||||
|
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||||
|
<label class="text-sm text-gray-600">조회방식</label>
|
||||||
|
<select name="mode" class="border border-gray-300 rounded px-2 py-1 text-sm">
|
||||||
|
<option value="daily" <?= ($mode ?? '') === 'daily' ? 'selected' : '' ?>>일자별</option>
|
||||||
|
<option value="period" <?= ($mode ?? '') === 'period' ? 'selected' : '' ?>>기간별</option>
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php if (($mode ?? 'daily') === 'daily'): ?>
|
||||||
|
<div class="border border-gray-300 overflow-auto mt-2">
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>판매일</th>
|
||||||
|
<th>판매소</th>
|
||||||
|
<th>봉투코드</th>
|
||||||
|
<th>봉투명</th>
|
||||||
|
<th>구분</th>
|
||||||
|
<th>수량</th>
|
||||||
|
<th>금액</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php foreach ($result as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><?= esc($row->bs_sale_date) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bs_ds_name) ?></td>
|
||||||
|
<td class="text-center font-mono"><?= esc($row->bs_bag_code) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bs_bag_name) ?></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<?php
|
||||||
|
$typeMap = ['sale' => '판매', 'return' => '반품'];
|
||||||
|
echo esc($typeMap[$row->bs_type] ?? $row->bs_type);
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<td><?= number_format((int) $row->total_qty) ?></td>
|
||||||
|
<td><?= number_format((int) $row->total_amount) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($result)): ?>
|
||||||
|
<tr><td colspan="7" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="border border-gray-300 overflow-auto mt-2">
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>판매소</th>
|
||||||
|
<th>봉투코드</th>
|
||||||
|
<th>봉투명</th>
|
||||||
|
<th>판매수량</th>
|
||||||
|
<th>판매금액</th>
|
||||||
|
<th>반품수량</th>
|
||||||
|
<th>반품금액</th>
|
||||||
|
<th>계(수량)</th>
|
||||||
|
<th>계(금액)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php foreach ($result as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bs_ds_name) ?></td>
|
||||||
|
<td class="text-center font-mono"><?= esc($row->bs_bag_code) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bs_bag_name) ?></td>
|
||||||
|
<td><?= number_format((int) $row->sale_qty) ?></td>
|
||||||
|
<td><?= number_format((int) $row->sale_amount) ?></td>
|
||||||
|
<td><?= number_format((int) $row->return_qty) ?></td>
|
||||||
|
<td><?= number_format((int) $row->return_amount) ?></td>
|
||||||
|
<td class="font-bold"><?= number_format((int) $row->sale_qty - (int) $row->return_qty) ?></td>
|
||||||
|
<td class="font-bold"><?= number_format((int) $row->sale_amount - (int) $row->return_amount) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($result)): ?>
|
||||||
|
<tr><td colspan="9" class="text-center text-gray-400 py-4">조회된 데이터가 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
130
app/Views/admin/sales_report/supply_demand.php
Normal file
130
app/Views/admin/sales_report/supply_demand.php
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||||
|
<span class="text-sm font-bold text-gray-700">봉투 수불 현황</span>
|
||||||
|
</section>
|
||||||
|
<section class="p-2 bg-white border-b border-gray-200">
|
||||||
|
<form method="GET" action="<?= base_url('admin/reports/supply-demand') ?>" class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="text-sm text-gray-600">시작일</label>
|
||||||
|
<input type="date" name="start_date" value="<?= esc($startDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||||
|
<label class="text-sm text-gray-600">~</label>
|
||||||
|
<input type="date" name="end_date" value="<?= esc($endDate ?? '') ?>" class="border border-gray-300 rounded px-2 py-1 text-sm"/>
|
||||||
|
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4 mt-2">
|
||||||
|
<!-- 현재 재고 -->
|
||||||
|
<div class="border border-gray-300 rounded overflow-auto">
|
||||||
|
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
|
||||||
|
<span class="text-sm font-bold text-gray-700">현재 재고</span>
|
||||||
|
</div>
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>봉투코드</th>
|
||||||
|
<th>봉투명</th>
|
||||||
|
<th>재고수량</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php foreach ($inventory as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center font-mono"><?= esc($row->bi_bag_code) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bi_bag_name) ?></td>
|
||||||
|
<td class="font-bold"><?= number_format((int) $row->bi_qty) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($inventory)): ?>
|
||||||
|
<tr><td colspan="3" class="text-center text-gray-400 py-4">데이터가 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 기간 입고 -->
|
||||||
|
<div class="border border-gray-300 rounded overflow-auto">
|
||||||
|
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
|
||||||
|
<span class="text-sm font-bold text-gray-700">기간 입고</span>
|
||||||
|
</div>
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>봉투코드</th>
|
||||||
|
<th>봉투명</th>
|
||||||
|
<th>입고수량</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php foreach ($receiving as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center font-mono"><?= esc($row->br_bag_code) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->br_bag_name) ?></td>
|
||||||
|
<td><?= number_format((int) $row->recv_qty) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($receiving)): ?>
|
||||||
|
<tr><td colspan="3" class="text-center text-gray-400 py-4">데이터가 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 기간 판매 -->
|
||||||
|
<div class="border border-gray-300 rounded overflow-auto">
|
||||||
|
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
|
||||||
|
<span class="text-sm font-bold text-gray-700">기간 판매</span>
|
||||||
|
</div>
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>봉투코드</th>
|
||||||
|
<th>봉투명</th>
|
||||||
|
<th>판매수량</th>
|
||||||
|
<th>반품수량</th>
|
||||||
|
<th>순판매</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php foreach ($sales as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center font-mono"><?= esc($row->bs_bag_code) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bs_bag_name) ?></td>
|
||||||
|
<td><?= number_format((int) $row->sale_qty) ?></td>
|
||||||
|
<td><?= number_format((int) $row->return_qty) ?></td>
|
||||||
|
<td class="font-bold"><?= number_format((int) $row->sale_qty - (int) $row->return_qty) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($sales)): ?>
|
||||||
|
<tr><td colspan="5" class="text-center text-gray-400 py-4">데이터가 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 기간 불출 -->
|
||||||
|
<div class="border border-gray-300 rounded overflow-auto">
|
||||||
|
<div class="bg-gray-100 border-b border-gray-300 px-3 py-1.5">
|
||||||
|
<span class="text-sm font-bold text-gray-700">기간 불출</span>
|
||||||
|
</div>
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>봉투코드</th>
|
||||||
|
<th>봉투명</th>
|
||||||
|
<th>불출수량</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php foreach ($issues as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center font-mono"><?= esc($row->bi2_bag_code) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bi2_bag_name) ?></td>
|
||||||
|
<td><?= number_format((int) $row->issue_qty) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($issues)): ?>
|
||||||
|
<tr><td colspan="3" class="text-center text-gray-400 py-4">데이터가 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
57
e2e/phase5-reports.spec.js
Normal file
57
e2e/phase5-reports.spec.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// @ts-check
|
||||||
|
const { test, expect } = require('@playwright/test');
|
||||||
|
const { login } = require('./helpers/auth');
|
||||||
|
|
||||||
|
async function loginAsAdmin(page) {
|
||||||
|
await login(page, 'admin');
|
||||||
|
await page.locator('input[name="lg_idx"]').first().check();
|
||||||
|
await page.click('button[type="submit"]');
|
||||||
|
await page.waitForURL(url => !url.pathname.includes('select-local-government'), { timeout: 15000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
test.describe('P5: 판매 대장', () => {
|
||||||
|
test('일자별 판매 대장 접근', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto('/admin/reports/sales-ledger');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/reports\/sales-ledger/);
|
||||||
|
});
|
||||||
|
test('기간별 판매 대장', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto('/admin/reports/sales-ledger?mode=period&start_date=2026-01-01&end_date=2026-12-31');
|
||||||
|
await expect(page).toHaveURL(/mode=period/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('P5: 일계표', () => {
|
||||||
|
test('일계표 접근', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto('/admin/reports/daily-summary');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/reports\/daily-summary/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('P5: 기간별 판매현황', () => {
|
||||||
|
test('기간별 판매현황 접근', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto('/admin/reports/period-sales');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/reports\/period-sales/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('P5: 봉투 수불 현황', () => {
|
||||||
|
test('수불 현황 접근', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto('/admin/reports/supply-demand');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/reports\/supply-demand/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('P5: 지자체관리자 접근', () => {
|
||||||
|
test('리포트 접근 가능', async ({ page }) => {
|
||||||
|
await login(page, 'local');
|
||||||
|
await page.goto('/admin/reports/sales-ledger');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/reports\/sales-ledger/);
|
||||||
|
await page.goto('/admin/reports/daily-summary');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/reports\/daily-summary/);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user