사용자 매뉴얼·번호알기·gov-portal 대시보드와 메뉴 동선·수불 리포트를 보강한다.
- 사용자 매뉴얼: league/commonmark 기반 bag/manual(로그인 전용), ManualRenderer + Config\Manual manifest, 콘텐츠 8종, E2E - 번호알기(봉투번호확인): bag/number-lookup, BagNumberLookup, E2E - gov-portal 대시보드 시안(기본/strip)·기본코드관리 화면 - 메뉴 관리: 등록·수정 후 메뉴 화면 유지, 수정 버튼 클릭 시 상단 스크롤 - 수불/분석 리포트(LOT 수불·반품/파기·수급계획·추이) 표시 보강 - .gitignore: docs/ → /docs/ 앵커링(최상위 개발문서만 제외, app/Docs는 추적) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,8 @@ namespace App\Libraries;
|
||||
|
||||
/**
|
||||
* 통계 분석 관리 (전년대비 / 월별·계절별 추이)
|
||||
*
|
||||
* 월별·계절별 추이·전년대비: bs_type = sale 판매량·판매금액만 집계 (반품·취소 제외)
|
||||
*/
|
||||
class BagAnalyticsReportBuilder
|
||||
{
|
||||
@@ -14,6 +16,12 @@ class BagAnalyticsReportBuilder
|
||||
/** @var array<string, string> */
|
||||
private array $bagNames = [];
|
||||
|
||||
/** 판매(bs_type=sale) 낱장 수량만 합산 */
|
||||
private function saleQtySql(): string
|
||||
{
|
||||
return "CASE WHEN bs.bs_type = 'sale' THEN ABS(bs.bs_qty) ELSE 0 END";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array{label: string, months_label: string, months: list<int>, cross_year: bool}>
|
||||
*/
|
||||
@@ -336,14 +344,11 @@ class BagAnalyticsReportBuilder
|
||||
string $gugunCode,
|
||||
int $dsIdx
|
||||
): array {
|
||||
$saleQty = $this->saleQtySql();
|
||||
$sql = "
|
||||
SELECT bs.bs_bag_code AS bag_code, YEAR(bs.bs_sale_date) AS y, MONTH(bs.bs_sale_date) AS m,
|
||||
SUM(CASE WHEN bs.bs_type = 'sale' THEN ABS(bs.bs_qty)
|
||||
WHEN bs.bs_type IN ('return','cancel') THEN -ABS(bs.bs_qty)
|
||||
ELSE 0 END) AS net_qty,
|
||||
SUM(CASE WHEN bs.bs_type = 'sale' THEN bs.bs_amount
|
||||
WHEN bs.bs_type IN ('return','cancel') THEN -ABS(bs.bs_amount)
|
||||
ELSE 0 END) AS net_amt
|
||||
SUM({$saleQty}) AS sale_qty,
|
||||
SUM(CASE WHEN bs.bs_type = 'sale' THEN bs.bs_amount ELSE 0 END) AS sale_amt
|
||||
FROM bag_sale bs
|
||||
INNER JOIN designated_shop ds ON ds.ds_idx = bs.bs_ds_idx
|
||||
WHERE bs.bs_lg_idx = ?
|
||||
@@ -369,8 +374,8 @@ class BagAnalyticsReportBuilder
|
||||
continue;
|
||||
}
|
||||
$agg[$code][$y][$m] = [
|
||||
'qty' => (float) ($row['net_qty'] ?? 0),
|
||||
'amt' => (float) ($row['net_amt'] ?? 0),
|
||||
'qty' => (float) ($row['sale_qty'] ?? 0),
|
||||
'amt' => (float) ($row['sale_amt'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -515,11 +520,10 @@ class BagAnalyticsReportBuilder
|
||||
private function monthlyNetByShop(int $lgIdx, int $year, int $month, string $gugunCode, int $saIdx = 0): array
|
||||
{
|
||||
$hasDsSa = $this->db->fieldExists('ds_sa_idx', 'designated_shop');
|
||||
$saleQty = $this->saleQtySql();
|
||||
$sql = "
|
||||
SELECT bs.bs_ds_idx AS ds_idx,
|
||||
SUM(CASE WHEN bs.bs_type = 'sale' THEN ABS(bs.bs_qty)
|
||||
WHEN bs.bs_type IN ('return','cancel') THEN -ABS(bs.bs_qty)
|
||||
ELSE 0 END) AS net_qty
|
||||
SUM({$saleQty}) AS sale_qty
|
||||
FROM bag_sale bs
|
||||
INNER JOIN designated_shop ds ON ds.ds_idx = bs.bs_ds_idx
|
||||
WHERE bs.bs_lg_idx = ? AND YEAR(bs.bs_sale_date) = ? AND MONTH(bs.bs_sale_date) = ?
|
||||
@@ -538,7 +542,7 @@ class BagAnalyticsReportBuilder
|
||||
|
||||
$map = [];
|
||||
foreach ($this->db->query($sql, $params)->getResultArray() as $row) {
|
||||
$map[(int) ($row['ds_idx'] ?? 0)] = (float) ($row['net_qty'] ?? 0);
|
||||
$map[(int) ($row['ds_idx'] ?? 0)] = (float) ($row['sale_qty'] ?? 0);
|
||||
}
|
||||
|
||||
return $map;
|
||||
@@ -560,11 +564,10 @@ class BagAnalyticsReportBuilder
|
||||
}
|
||||
|
||||
$hasDsSa = $this->db->fieldExists('ds_sa_idx', 'designated_shop');
|
||||
$saleQty = $this->saleQtySql();
|
||||
$sql = "
|
||||
SELECT bs.bs_ds_idx AS ds_idx,
|
||||
SUM(CASE WHEN bs.bs_type = 'sale' THEN ABS(bs.bs_qty)
|
||||
WHEN bs.bs_type IN ('return','cancel') THEN -ABS(bs.bs_qty)
|
||||
ELSE 0 END) / 12 AS avg_qty
|
||||
SUM({$saleQty}) / 12 AS avg_qty
|
||||
FROM bag_sale bs
|
||||
INNER JOIN designated_shop ds ON ds.ds_idx = bs.bs_ds_idx
|
||||
WHERE bs.bs_lg_idx = ? AND YEAR(bs.bs_sale_date) = ?
|
||||
@@ -606,15 +609,13 @@ class BagAnalyticsReportBuilder
|
||||
}
|
||||
|
||||
$divisor = count($months);
|
||||
$qtyExpr = "CASE WHEN bs.bs_type = 'sale' THEN ABS(bs.bs_qty)
|
||||
WHEN bs.bs_type IN ('return','cancel') THEN -ABS(bs.bs_qty)
|
||||
ELSE 0 END";
|
||||
$saleQty = $this->saleQtySql();
|
||||
$hasDsSa = $this->db->fieldExists('ds_sa_idx', 'designated_shop');
|
||||
|
||||
if ($crossYearWinter) {
|
||||
$sql = "
|
||||
SELECT bs.bs_ds_idx AS ds_idx,
|
||||
SUM({$qtyExpr}) / ? AS avg_qty
|
||||
SUM({$saleQty}) / ? AS avg_qty
|
||||
FROM bag_sale bs
|
||||
INNER JOIN designated_shop ds ON ds.ds_idx = bs.bs_ds_idx
|
||||
WHERE bs.bs_lg_idx = ?
|
||||
@@ -629,7 +630,7 @@ class BagAnalyticsReportBuilder
|
||||
$placeholders = implode(',', array_fill(0, count($months), '?'));
|
||||
$sql = "
|
||||
SELECT bs.bs_ds_idx AS ds_idx,
|
||||
SUM({$qtyExpr}) / ? AS avg_qty
|
||||
SUM({$saleQty}) / ? AS avg_qty
|
||||
FROM bag_sale bs
|
||||
INNER JOIN designated_shop ds ON ds.ds_idx = bs.bs_ds_idx
|
||||
WHERE bs.bs_lg_idx = ? AND YEAR(bs.bs_sale_date) = ?
|
||||
|
||||
Reference in New Issue
Block a user