Phase 4 주문접수/판매/반품/불출 관리 구현
- DB: shop_order, shop_order_item, bag_sale, bag_issue 테이블 - 주문접수: 지정판매소 선택, 품목별 수량, 소비자가 연동, 포장단위 환산 - 접수/취소, 배달일 기간 필터 - 판매/반품: 지정판매소별 봉투 판매+반품, 재고 자동 감산/가산 - 무료용 불출: 연도/분기/불출처/봉투코드, 재고 감산, 취소 시 복원 - E2E 테스트 7개 전체 통과 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -97,6 +97,23 @@ $routes->group('admin', ['filter' => 'adminAuth'], static function ($routes): vo
|
|||||||
// 재고 현황 (P3-10)
|
// 재고 현황 (P3-10)
|
||||||
$routes->get('bag-inventory', 'Admin\BagInventory::index');
|
$routes->get('bag-inventory', 'Admin\BagInventory::index');
|
||||||
|
|
||||||
|
// 주문 접수 관리 (P4-01~03)
|
||||||
|
$routes->get('shop-orders', 'Admin\ShopOrder::index');
|
||||||
|
$routes->get('shop-orders/create', 'Admin\ShopOrder::create');
|
||||||
|
$routes->post('shop-orders/store', 'Admin\ShopOrder::store');
|
||||||
|
$routes->post('shop-orders/cancel/(:num)', 'Admin\ShopOrder::cancel/$1');
|
||||||
|
|
||||||
|
// 판매/반품 관리 (P4-04~07)
|
||||||
|
$routes->get('bag-sales', 'Admin\BagSale::index');
|
||||||
|
$routes->get('bag-sales/create', 'Admin\BagSale::create');
|
||||||
|
$routes->post('bag-sales/store', 'Admin\BagSale::store');
|
||||||
|
|
||||||
|
// 무료용 불출 관리 (P4-08~10)
|
||||||
|
$routes->get('bag-issues', 'Admin\BagIssue::index');
|
||||||
|
$routes->get('bag-issues/create', 'Admin\BagIssue::create');
|
||||||
|
$routes->post('bag-issues/store', 'Admin\BagIssue::store');
|
||||||
|
$routes->post('bag-issues/cancel/(:num)', 'Admin\BagIssue::cancel/$1');
|
||||||
|
|
||||||
// 포장 단위 관리 (P2-05/06)
|
// 포장 단위 관리 (P2-05/06)
|
||||||
$routes->get('packaging-units', 'Admin\PackagingUnit::index');
|
$routes->get('packaging-units', 'Admin\PackagingUnit::index');
|
||||||
$routes->get('packaging-units/create', 'Admin\PackagingUnit::create');
|
$routes->get('packaging-units/create', 'Admin\PackagingUnit::create');
|
||||||
|
|||||||
122
app/Controllers/Admin/BagIssue.php
Normal file
122
app/Controllers/Admin/BagIssue.php
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Controllers\BaseController;
|
||||||
|
use App\Models\BagIssueModel;
|
||||||
|
use App\Models\BagInventoryModel;
|
||||||
|
use App\Models\CodeKindModel;
|
||||||
|
use App\Models\CodeDetailModel;
|
||||||
|
|
||||||
|
class BagIssue extends BaseController
|
||||||
|
{
|
||||||
|
private BagIssueModel $issueModel;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->issueModel = model(BagIssueModel::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||||
|
|
||||||
|
$builder = $this->issueModel->where('bi2_lg_idx', $lgIdx);
|
||||||
|
$startDate = $this->request->getGet('start_date');
|
||||||
|
$endDate = $this->request->getGet('end_date');
|
||||||
|
if ($startDate) $builder->where('bi2_issue_date >=', $startDate);
|
||||||
|
if ($endDate) $builder->where('bi2_issue_date <=', $endDate);
|
||||||
|
|
||||||
|
$list = $builder->orderBy('bi2_issue_date', 'DESC')->orderBy('bi2_idx', 'DESC')->findAll();
|
||||||
|
|
||||||
|
return view('admin/layout', [
|
||||||
|
'title' => '무료용 불출 관리',
|
||||||
|
'content' => view('admin/bag_issue/index', compact('list', 'startDate', 'endDate')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||||
|
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true) : [];
|
||||||
|
|
||||||
|
return view('admin/layout', [
|
||||||
|
'title' => '무료용 불출 처리',
|
||||||
|
'content' => view('admin/bag_issue/create', compact('bagCodes')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'bi2_year' => 'required|is_natural_no_zero',
|
||||||
|
'bi2_quarter' => 'required|in_list[1,2,3,4]',
|
||||||
|
'bi2_issue_type' => 'required|max_length[20]',
|
||||||
|
'bi2_issue_date' => 'required|valid_date[Y-m-d]',
|
||||||
|
'bi2_dest_name' => 'required|max_length[100]',
|
||||||
|
'bi2_bag_code' => 'required|max_length[50]',
|
||||||
|
'bi2_qty' => 'required|is_natural_no_zero',
|
||||||
|
];
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$bagCode = $this->request->getPost('bi2_bag_code');
|
||||||
|
$qty = (int) $this->request->getPost('bi2_qty');
|
||||||
|
|
||||||
|
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||||
|
$detail = $kindO ? model(CodeDetailModel::class)->where('cd_ck_idx', $kindO->ck_idx)->where('cd_code', $bagCode)->first() : null;
|
||||||
|
$bagName = $detail ? $detail->cd_name : '';
|
||||||
|
|
||||||
|
$db = \Config\Database::connect();
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
$this->issueModel->insert([
|
||||||
|
'bi2_lg_idx' => $lgIdx,
|
||||||
|
'bi2_year' => (int) $this->request->getPost('bi2_year'),
|
||||||
|
'bi2_quarter' => (int) $this->request->getPost('bi2_quarter'),
|
||||||
|
'bi2_issue_type' => $this->request->getPost('bi2_issue_type'),
|
||||||
|
'bi2_issue_date' => $this->request->getPost('bi2_issue_date'),
|
||||||
|
'bi2_dest_type' => $this->request->getPost('bi2_dest_type') ?? '',
|
||||||
|
'bi2_dest_name' => $this->request->getPost('bi2_dest_name'),
|
||||||
|
'bi2_bag_code' => $bagCode,
|
||||||
|
'bi2_bag_name' => $bagName,
|
||||||
|
'bi2_qty' => $qty,
|
||||||
|
'bi2_status' => 'normal',
|
||||||
|
'bi2_regdate' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 재고 감산
|
||||||
|
model(BagInventoryModel::class)->adjustQty($lgIdx, $bagCode, $bagName, -$qty);
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
return redirect()->to(site_url('admin/bag-issues'))->with('success', '불출 처리되었습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancel(int $id)
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$item = $this->issueModel->find($id);
|
||||||
|
if (!$item || (int) $item->bi2_lg_idx !== admin_effective_lg_idx()) {
|
||||||
|
return redirect()->to(site_url('admin/bag-issues'))->with('error', '불출 내역을 찾을 수 없습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = \Config\Database::connect();
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
$this->issueModel->update($id, ['bi2_status' => 'cancelled']);
|
||||||
|
// 재고 복원
|
||||||
|
model(BagInventoryModel::class)->adjustQty((int) $item->bi2_lg_idx, $item->bi2_bag_code, $item->bi2_bag_name, (int) $item->bi2_qty);
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
return redirect()->to(site_url('admin/bag-issues'))->with('success', '불출이 취소되었습니다.');
|
||||||
|
}
|
||||||
|
}
|
||||||
114
app/Controllers/Admin/BagSale.php
Normal file
114
app/Controllers/Admin/BagSale.php
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Controllers\BaseController;
|
||||||
|
use App\Models\BagSaleModel;
|
||||||
|
use App\Models\BagInventoryModel;
|
||||||
|
use App\Models\DesignatedShopModel;
|
||||||
|
use App\Models\CodeKindModel;
|
||||||
|
use App\Models\CodeDetailModel;
|
||||||
|
use App\Models\BagPriceModel;
|
||||||
|
|
||||||
|
class BagSale extends BaseController
|
||||||
|
{
|
||||||
|
private BagSaleModel $saleModel;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->saleModel = model(BagSaleModel::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||||
|
|
||||||
|
$builder = $this->saleModel->where('bs_lg_idx', $lgIdx);
|
||||||
|
$startDate = $this->request->getGet('start_date');
|
||||||
|
$endDate = $this->request->getGet('end_date');
|
||||||
|
$type = $this->request->getGet('type');
|
||||||
|
if ($startDate) $builder->where('bs_sale_date >=', $startDate);
|
||||||
|
if ($endDate) $builder->where('bs_sale_date <=', $endDate);
|
||||||
|
if ($type) $builder->where('bs_type', $type);
|
||||||
|
|
||||||
|
$list = $builder->orderBy('bs_sale_date', 'DESC')->orderBy('bs_idx', 'DESC')->findAll();
|
||||||
|
|
||||||
|
return view('admin/layout', [
|
||||||
|
'title' => '판매/반품 관리',
|
||||||
|
'content' => view('admin/bag_sale/index', compact('list', 'startDate', 'endDate', 'type')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
if (!$lgIdx) return redirect()->to(site_url('admin/bag-sales'))->with('error', '지자체를 선택해 주세요.');
|
||||||
|
|
||||||
|
$shops = model(DesignatedShopModel::class)->where('ds_lg_idx', $lgIdx)->where('ds_state', 1)->findAll();
|
||||||
|
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||||
|
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true) : [];
|
||||||
|
|
||||||
|
return view('admin/layout', [
|
||||||
|
'title' => '판매 등록',
|
||||||
|
'content' => view('admin/bag_sale/create', compact('shops', 'bagCodes')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'bs_ds_idx' => 'required|is_natural_no_zero',
|
||||||
|
'bs_bag_code' => 'required|max_length[50]',
|
||||||
|
'bs_qty' => 'required|is_natural_no_zero',
|
||||||
|
'bs_sale_date' => 'required|valid_date[Y-m-d]',
|
||||||
|
'bs_type' => 'required|in_list[sale,return]',
|
||||||
|
];
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$dsIdx = (int) $this->request->getPost('bs_ds_idx');
|
||||||
|
$bagCode = $this->request->getPost('bs_bag_code');
|
||||||
|
$qty = (int) $this->request->getPost('bs_qty');
|
||||||
|
$type = $this->request->getPost('bs_type');
|
||||||
|
|
||||||
|
$shop = model(DesignatedShopModel::class)->find($dsIdx);
|
||||||
|
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||||
|
$detail = $kindO ? model(CodeDetailModel::class)->where('cd_ck_idx', $kindO->ck_idx)->where('cd_code', $bagCode)->first() : null;
|
||||||
|
$price = model(BagPriceModel::class)->where('bp_lg_idx', $lgIdx)->where('bp_bag_code', $bagCode)->where('bp_state', 1)->first();
|
||||||
|
$unitPrice = $price ? (float) $price->bp_consumer : 0;
|
||||||
|
|
||||||
|
$actualQty = ($type === 'return') ? -$qty : $qty;
|
||||||
|
|
||||||
|
$db = \Config\Database::connect();
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
$this->saleModel->insert([
|
||||||
|
'bs_lg_idx' => $lgIdx,
|
||||||
|
'bs_ds_idx' => $dsIdx,
|
||||||
|
'bs_ds_name' => $shop ? $shop->ds_name : '',
|
||||||
|
'bs_sale_date' => $this->request->getPost('bs_sale_date'),
|
||||||
|
'bs_bag_code' => $bagCode,
|
||||||
|
'bs_bag_name' => $detail ? $detail->cd_name : '',
|
||||||
|
'bs_qty' => $actualQty,
|
||||||
|
'bs_unit_price'=> $unitPrice,
|
||||||
|
'bs_amount' => $unitPrice * abs($actualQty),
|
||||||
|
'bs_type' => $type,
|
||||||
|
'bs_regdate' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 재고 감산(판매) / 가산(반품)
|
||||||
|
model(BagInventoryModel::class)->adjustQty($lgIdx, $bagCode, $detail ? $detail->cd_name : '', -$actualQty);
|
||||||
|
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
$msg = ($type === 'sale') ? '판매 처리되었습니다.' : '반품 처리되었습니다.';
|
||||||
|
return redirect()->to(site_url('admin/bag-sales'))->with('success', $msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
153
app/Controllers/Admin/ShopOrder.php
Normal file
153
app/Controllers/Admin/ShopOrder.php
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Controllers\BaseController;
|
||||||
|
use App\Models\ShopOrderModel;
|
||||||
|
use App\Models\ShopOrderItemModel;
|
||||||
|
use App\Models\DesignatedShopModel;
|
||||||
|
use App\Models\BagPriceModel;
|
||||||
|
use App\Models\PackagingUnitModel;
|
||||||
|
use App\Models\CodeKindModel;
|
||||||
|
use App\Models\CodeDetailModel;
|
||||||
|
|
||||||
|
class ShopOrder extends BaseController
|
||||||
|
{
|
||||||
|
private ShopOrderModel $orderModel;
|
||||||
|
private ShopOrderItemModel $itemModel;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->orderModel = model(ShopOrderModel::class);
|
||||||
|
$this->itemModel = model(ShopOrderItemModel::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
if (!$lgIdx) return redirect()->to(site_url('admin'))->with('error', '지자체를 선택해 주세요.');
|
||||||
|
|
||||||
|
$builder = $this->orderModel->where('so_lg_idx', $lgIdx);
|
||||||
|
$startDate = $this->request->getGet('start_date');
|
||||||
|
$endDate = $this->request->getGet('end_date');
|
||||||
|
if ($startDate) $builder->where('so_delivery_date >=', $startDate);
|
||||||
|
if ($endDate) $builder->where('so_delivery_date <=', $endDate);
|
||||||
|
|
||||||
|
$list = $builder->orderBy('so_idx', 'DESC')->findAll();
|
||||||
|
|
||||||
|
return view('admin/layout', [
|
||||||
|
'title' => '주문 접수 관리',
|
||||||
|
'content' => view('admin/shop_order/index', compact('list', 'startDate', 'endDate')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
if (!$lgIdx) return redirect()->to(site_url('admin/shop-orders'))->with('error', '지자체를 선택해 주세요.');
|
||||||
|
|
||||||
|
$shops = model(DesignatedShopModel::class)->where('ds_lg_idx', $lgIdx)->where('ds_state', 1)->findAll();
|
||||||
|
$kind = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||||
|
$bagCodes = $kind ? model(CodeDetailModel::class)->getByKind((int) $kind->ck_idx, true) : [];
|
||||||
|
|
||||||
|
return view('admin/layout', [
|
||||||
|
'title' => '주문 접수',
|
||||||
|
'content' => view('admin/shop_order/create', compact('shops', 'bagCodes')),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store()
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$lgIdx = admin_effective_lg_idx();
|
||||||
|
|
||||||
|
$rules = [
|
||||||
|
'so_ds_idx' => 'required|is_natural_no_zero',
|
||||||
|
'so_delivery_date'=> 'required|valid_date[Y-m-d]',
|
||||||
|
'so_payment_type' => 'required|in_list[이체,가상계좌]',
|
||||||
|
];
|
||||||
|
if (! $this->validate($rules)) {
|
||||||
|
return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
|
||||||
|
}
|
||||||
|
|
||||||
|
$db = \Config\Database::connect();
|
||||||
|
$db->transStart();
|
||||||
|
|
||||||
|
$dsIdx = (int) $this->request->getPost('so_ds_idx');
|
||||||
|
$shop = model(DesignatedShopModel::class)->find($dsIdx);
|
||||||
|
|
||||||
|
$this->orderModel->insert([
|
||||||
|
'so_lg_idx' => $lgIdx,
|
||||||
|
'so_ds_idx' => $dsIdx,
|
||||||
|
'so_ds_name' => $shop ? $shop->ds_name : '',
|
||||||
|
'so_order_date' => date('Y-m-d'),
|
||||||
|
'so_delivery_date'=> $this->request->getPost('so_delivery_date'),
|
||||||
|
'so_payment_type' => $this->request->getPost('so_payment_type'),
|
||||||
|
'so_status' => 'normal',
|
||||||
|
'so_orderer_idx' => session()->get('mb_idx'),
|
||||||
|
'so_regdate' => date('Y-m-d H:i:s'),
|
||||||
|
]);
|
||||||
|
$soIdx = (int) $this->orderModel->getInsertID();
|
||||||
|
|
||||||
|
$bagCodes = $this->request->getPost('item_bag_code') ?? [];
|
||||||
|
$qtys = $this->request->getPost('item_qty') ?? [];
|
||||||
|
$totalQty = 0; $totalAmt = 0;
|
||||||
|
|
||||||
|
foreach ($bagCodes as $i => $code) {
|
||||||
|
if (empty($code) || empty($qtys[$i])) continue;
|
||||||
|
$qty = (int) $qtys[$i];
|
||||||
|
|
||||||
|
$price = model(BagPriceModel::class)->where('bp_lg_idx', $lgIdx)->where('bp_bag_code', $code)->where('bp_state', 1)->first();
|
||||||
|
$unitPrice = $price ? (float) $price->bp_consumer : 0;
|
||||||
|
$amount = $unitPrice * $qty;
|
||||||
|
|
||||||
|
$unit = model(PackagingUnitModel::class)->where('pu_lg_idx', $lgIdx)->where('pu_bag_code', $code)->where('pu_state', 1)->first();
|
||||||
|
$boxCount = 0; $packCount = 0; $sheetCount = $qty;
|
||||||
|
if ($unit && (int) $unit->pu_total_per_box > 0) {
|
||||||
|
$boxCount = intdiv($qty, (int) $unit->pu_total_per_box);
|
||||||
|
$remainder = $qty % (int) $unit->pu_total_per_box;
|
||||||
|
if ((int) $unit->pu_pack_per_sheet > 0) {
|
||||||
|
$packCount = intdiv($remainder, (int) $unit->pu_pack_per_sheet);
|
||||||
|
$sheetCount = $remainder % (int) $unit->pu_pack_per_sheet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$kindO = model(CodeKindModel::class)->where('ck_code', 'O')->first();
|
||||||
|
$detail = $kindO ? model(CodeDetailModel::class)->where('cd_ck_idx', $kindO->ck_idx)->where('cd_code', $code)->first() : null;
|
||||||
|
|
||||||
|
$this->itemModel->insert([
|
||||||
|
'soi_so_idx' => $soIdx,
|
||||||
|
'soi_bag_code' => $code,
|
||||||
|
'soi_bag_name' => $detail ? $detail->cd_name : '',
|
||||||
|
'soi_unit_price' => $unitPrice,
|
||||||
|
'soi_qty' => $qty,
|
||||||
|
'soi_amount' => $amount,
|
||||||
|
'soi_box_count' => $boxCount,
|
||||||
|
'soi_pack_count' => $packCount,
|
||||||
|
'soi_sheet_count'=> $sheetCount,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$totalQty += $qty;
|
||||||
|
$totalAmt += $amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->orderModel->update($soIdx, ['so_total_qty' => $totalQty, 'so_total_amount' => $totalAmt]);
|
||||||
|
$db->transComplete();
|
||||||
|
|
||||||
|
return redirect()->to(site_url('admin/shop-orders'))->with('success', '주문이 접수되었습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function cancel(int $id)
|
||||||
|
{
|
||||||
|
helper('admin');
|
||||||
|
$order = $this->orderModel->find($id);
|
||||||
|
if (!$order || (int) $order->so_lg_idx !== admin_effective_lg_idx()) {
|
||||||
|
return redirect()->to(site_url('admin/shop-orders'))->with('error', '주문을 찾을 수 없습니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->orderModel->update($id, ['so_status' => 'cancelled']);
|
||||||
|
return redirect()->to(site_url('admin/shop-orders'))->with('success', '주문이 취소되었습니다.');
|
||||||
|
}
|
||||||
|
}
|
||||||
18
app/Models/BagIssueModel.php
Normal file
18
app/Models/BagIssueModel.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use CodeIgniter\Model;
|
||||||
|
|
||||||
|
class BagIssueModel extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'bag_issue';
|
||||||
|
protected $primaryKey = 'bi2_idx';
|
||||||
|
protected $returnType = 'object';
|
||||||
|
protected $useTimestamps = false;
|
||||||
|
protected $allowedFields = [
|
||||||
|
'bi2_lg_idx', 'bi2_year', 'bi2_quarter', 'bi2_issue_type', 'bi2_issue_date',
|
||||||
|
'bi2_dest_type', 'bi2_dest_name', 'bi2_bag_code', 'bi2_bag_name',
|
||||||
|
'bi2_qty', 'bi2_status', 'bi2_regdate',
|
||||||
|
];
|
||||||
|
}
|
||||||
18
app/Models/BagSaleModel.php
Normal file
18
app/Models/BagSaleModel.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use CodeIgniter\Model;
|
||||||
|
|
||||||
|
class BagSaleModel extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'bag_sale';
|
||||||
|
protected $primaryKey = 'bs_idx';
|
||||||
|
protected $returnType = 'object';
|
||||||
|
protected $useTimestamps = false;
|
||||||
|
protected $allowedFields = [
|
||||||
|
'bs_lg_idx', 'bs_so_idx', 'bs_ds_idx', 'bs_ds_name', 'bs_sale_date',
|
||||||
|
'bs_bag_code', 'bs_bag_name', 'bs_qty', 'bs_unit_price', 'bs_amount',
|
||||||
|
'bs_type', 'bs_regdate',
|
||||||
|
];
|
||||||
|
}
|
||||||
17
app/Models/ShopOrderItemModel.php
Normal file
17
app/Models/ShopOrderItemModel.php
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use CodeIgniter\Model;
|
||||||
|
|
||||||
|
class ShopOrderItemModel extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'shop_order_item';
|
||||||
|
protected $primaryKey = 'soi_idx';
|
||||||
|
protected $returnType = 'object';
|
||||||
|
protected $useTimestamps = false;
|
||||||
|
protected $allowedFields = [
|
||||||
|
'soi_so_idx', 'soi_bag_code', 'soi_bag_name', 'soi_unit_price',
|
||||||
|
'soi_qty', 'soi_amount', 'soi_box_count', 'soi_pack_count', 'soi_sheet_count',
|
||||||
|
];
|
||||||
|
}
|
||||||
18
app/Models/ShopOrderModel.php
Normal file
18
app/Models/ShopOrderModel.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use CodeIgniter\Model;
|
||||||
|
|
||||||
|
class ShopOrderModel extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'shop_order';
|
||||||
|
protected $primaryKey = 'so_idx';
|
||||||
|
protected $returnType = 'object';
|
||||||
|
protected $useTimestamps = false;
|
||||||
|
protected $allowedFields = [
|
||||||
|
'so_lg_idx', 'so_ds_idx', 'so_ds_name', 'so_order_date', 'so_delivery_date',
|
||||||
|
'so_payment_type', 'so_paid', 'so_received', 'so_total_qty', 'so_total_amount',
|
||||||
|
'so_status', 'so_orderer_idx', 'so_regdate',
|
||||||
|
];
|
||||||
|
}
|
||||||
70
app/Views/admin/bag_issue/create.php
Normal file
70
app/Views/admin/bag_issue/create.php
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<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>
|
||||||
|
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||||
|
<form action="<?= base_url('admin/bag-issues/store') ?>" method="POST" class="space-y-4">
|
||||||
|
<?= csrf_field() ?>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">연도 <span class="text-red-500">*</span></label>
|
||||||
|
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32 text-right" name="bi2_year" type="number" min="2000" max="2099" value="<?= esc(old('bi2_year', date('Y'))) ?>" required/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">분기 <span class="text-red-500">*</span></label>
|
||||||
|
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32" name="bi2_quarter" required>
|
||||||
|
<option value="">선택</option>
|
||||||
|
<option value="1" <?= old('bi2_quarter') === '1' ? 'selected' : '' ?>>1</option>
|
||||||
|
<option value="2" <?= old('bi2_quarter') === '2' ? 'selected' : '' ?>>2</option>
|
||||||
|
<option value="3" <?= old('bi2_quarter') === '3' ? 'selected' : '' ?>>3</option>
|
||||||
|
<option value="4" <?= old('bi2_quarter') === '4' ? 'selected' : '' ?>>4</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">구분 <span class="text-red-500">*</span></label>
|
||||||
|
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="bi2_issue_type" required>
|
||||||
|
<option value="">선택</option>
|
||||||
|
<option value="무료용" <?= old('bi2_issue_type') === '무료용' ? 'selected' : '' ?>>무료용</option>
|
||||||
|
<option value="공공용" <?= old('bi2_issue_type') === '공공용' ? 'selected' : '' ?>>공공용</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">불출일 <span class="text-red-500">*</span></label>
|
||||||
|
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="bi2_issue_date" type="date" value="<?= esc(old('bi2_issue_date', date('Y-m-d'))) ?>" required/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">불출처 유형</label>
|
||||||
|
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="bi2_dest_type" type="text" placeholder="동사무소" value="<?= esc(old('bi2_dest_type')) ?>"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">불출처명 <span class="text-red-500">*</span></label>
|
||||||
|
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="bi2_dest_name" type="text" value="<?= esc(old('bi2_dest_name')) ?>" required/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">봉투코드 <span class="text-red-500">*</span></label>
|
||||||
|
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="bi2_bag_code" required>
|
||||||
|
<option value="">선택</option>
|
||||||
|
<?php foreach ($bagCodes as $cd): ?>
|
||||||
|
<option value="<?= esc($cd->cd_code) ?>" <?= old('bi2_bag_code') === $cd->cd_code ? 'selected' : '' ?>>
|
||||||
|
<?= esc($cd->cd_code) ?> — <?= esc($cd->cd_name) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">수량 <span class="text-red-500">*</span></label>
|
||||||
|
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32 text-right" name="bi2_qty" type="number" min="0" value="<?= esc(old('bi2_qty', '0')) ?>" required/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 pt-2">
|
||||||
|
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||||
|
<a href="<?= base_url('admin/bag-issues') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
60
app/Views/admin/bag_issue/index.php
Normal file
60
app/Views/admin/bag_issue/index.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||||
|
<span class="text-sm font-bold text-gray-700">무료용 불출 관리</span>
|
||||||
|
<a href="<?= base_url('admin/bag-issues/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">불출 처리</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="p-2 bg-white border-b border-gray-200">
|
||||||
|
<form method="GET" action="<?= base_url('admin/bag-issues') ?>" 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>
|
||||||
|
<a href="<?= base_url('admin/bag-issues') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<div class="border border-gray-300 overflow-auto mt-2">
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="w-16">번호</th>
|
||||||
|
<th>연도</th>
|
||||||
|
<th>분기</th>
|
||||||
|
<th>구분</th>
|
||||||
|
<th>불출일</th>
|
||||||
|
<th>불출처</th>
|
||||||
|
<th>봉투코드</th>
|
||||||
|
<th>봉투명</th>
|
||||||
|
<th>수량</th>
|
||||||
|
<th class="w-20">상태</th>
|
||||||
|
<th class="w-24">작업</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php foreach ($list as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><?= esc($row->bi2_idx) ?></td>
|
||||||
|
<td class="text-center"><?= esc($row->bi2_year) ?></td>
|
||||||
|
<td class="text-center"><?= esc($row->bi2_quarter) ?></td>
|
||||||
|
<td class="text-center"><?= esc($row->bi2_issue_type) ?></td>
|
||||||
|
<td class="text-center"><?= esc($row->bi2_issue_date) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bi2_dest_name) ?></td>
|
||||||
|
<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->bi2_qty) ?></td>
|
||||||
|
<td class="text-center"><?= esc($row->bi2_status) ?></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<form action="<?= base_url('admin/bag-issues/cancel/' . (int) $row->bi2_idx) ?>" method="POST" class="inline" onsubmit="return confirm('취소하시겠습니까?');">
|
||||||
|
<?= csrf_field() ?>
|
||||||
|
<button type="submit" class="text-orange-600 hover:underline text-sm">취소</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($list)): ?>
|
||||||
|
<tr><td colspan="11" class="text-center text-gray-400 py-4">등록된 불출이 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
56
app/Views/admin/bag_sale/create.php
Normal file
56
app/Views/admin/bag_sale/create.php
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<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>
|
||||||
|
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-3xl">
|
||||||
|
<form action="<?= base_url('admin/bag-sales/store') ?>" method="POST" class="space-y-4">
|
||||||
|
<?= csrf_field() ?>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">판매소 <span class="text-red-500">*</span></label>
|
||||||
|
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="bs_ds_idx" required>
|
||||||
|
<option value="">선택</option>
|
||||||
|
<?php foreach ($shops as $shop): ?>
|
||||||
|
<option value="<?= esc($shop->ds_idx) ?>" <?= (int) old('bs_ds_idx') === (int) $shop->ds_idx ? 'selected' : '' ?>>
|
||||||
|
<?= esc($shop->ds_name) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">봉투코드 <span class="text-red-500">*</span></label>
|
||||||
|
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="bs_bag_code" required>
|
||||||
|
<option value="">선택</option>
|
||||||
|
<?php foreach ($bagCodes as $cd): ?>
|
||||||
|
<option value="<?= esc($cd->cd_code) ?>" <?= old('bs_bag_code') === $cd->cd_code ? 'selected' : '' ?>>
|
||||||
|
<?= esc($cd->cd_code) ?> — <?= esc($cd->cd_name) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">수량 <span class="text-red-500">*</span></label>
|
||||||
|
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-32 text-right" name="bs_qty" type="number" min="0" value="<?= esc(old('bs_qty', '0')) ?>" required/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">판매일 <span class="text-red-500">*</span></label>
|
||||||
|
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="bs_sale_date" type="date" value="<?= esc(old('bs_sale_date', date('Y-m-d'))) ?>" required/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">구분 <span class="text-red-500">*</span></label>
|
||||||
|
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="bs_type" required>
|
||||||
|
<option value="">선택</option>
|
||||||
|
<option value="sale" <?= old('bs_type') === 'sale' ? 'selected' : '' ?>>판매</option>
|
||||||
|
<option value="return" <?= old('bs_type') === 'return' ? 'selected' : '' ?>>반품</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 pt-2">
|
||||||
|
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||||
|
<a href="<?= base_url('admin/bag-sales') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
63
app/Views/admin/bag_sale/index.php
Normal file
63
app/Views/admin/bag_sale/index.php
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||||
|
<span class="text-sm font-bold text-gray-700">판매/반품 관리</span>
|
||||||
|
<a href="<?= base_url('admin/bag-sales/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">판매 등록</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="p-2 bg-white border-b border-gray-200">
|
||||||
|
<form method="GET" action="<?= base_url('admin/bag-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"/>
|
||||||
|
<label class="text-sm text-gray-600">구분</label>
|
||||||
|
<select name="type" class="border border-gray-300 rounded px-2 py-1 text-sm">
|
||||||
|
<option value="">전체</option>
|
||||||
|
<option value="sale" <?= ($type ?? '') === 'sale' ? 'selected' : '' ?>>판매</option>
|
||||||
|
<option value="return" <?= ($type ?? '') === 'return' ? 'selected' : '' ?>>반품</option>
|
||||||
|
<option value="cancel" <?= ($type ?? '') === 'cancel' ? 'selected' : '' ?>>취소</option>
|
||||||
|
</select>
|
||||||
|
<button type="submit" class="bg-btn-search text-white px-4 py-1 rounded-sm text-sm">조회</button>
|
||||||
|
<a href="<?= base_url('admin/bag-sales') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<div class="border border-gray-300 overflow-auto mt-2">
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="w-16">번호</th>
|
||||||
|
<th>판매소</th>
|
||||||
|
<th>판매일</th>
|
||||||
|
<th>봉투코드</th>
|
||||||
|
<th>봉투명</th>
|
||||||
|
<th>수량</th>
|
||||||
|
<th>단가</th>
|
||||||
|
<th>금액</th>
|
||||||
|
<th class="w-20">구분</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php foreach ($list as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><?= esc($row->bs_idx) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->bs_ds_name) ?></td>
|
||||||
|
<td class="text-center"><?= esc($row->bs_sale_date) ?></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->bs_qty) ?></td>
|
||||||
|
<td><?= number_format((int) $row->bs_unit_price) ?></td>
|
||||||
|
<td><?= number_format((int) $row->bs_amount) ?></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<?php
|
||||||
|
$typeMap = ['sale' => '판매', 'return' => '반품', 'cancel' => '취소'];
|
||||||
|
echo esc($typeMap[$row->bs_type] ?? $row->bs_type);
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($list)): ?>
|
||||||
|
<tr><td colspan="9" class="text-center text-gray-400 py-4">등록된 판매/반품이 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
74
app/Views/admin/shop_order/create.php
Normal file
74
app/Views/admin/shop_order/create.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<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>
|
||||||
|
<div class="border border-gray-300 p-4 mt-2 bg-white max-w-4xl">
|
||||||
|
<form action="<?= base_url('admin/shop-orders/store') ?>" method="POST" class="space-y-4">
|
||||||
|
<?= csrf_field() ?>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">판매소 <span class="text-red-500">*</span></label>
|
||||||
|
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-60" name="so_ds_idx" required>
|
||||||
|
<option value="">선택</option>
|
||||||
|
<?php foreach ($shops as $shop): ?>
|
||||||
|
<option value="<?= esc($shop->ds_idx) ?>" <?= (int) old('so_ds_idx') === (int) $shop->ds_idx ? 'selected' : '' ?>>
|
||||||
|
<?= esc($shop->ds_name) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">배달일 <span class="text-red-500">*</span></label>
|
||||||
|
<input class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="so_delivery_date" type="date" value="<?= esc(old('so_delivery_date', date('Y-m-d', strtotime('+1 day')))) ?>" required/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 w-28">결제방법 <span class="text-red-500">*</span></label>
|
||||||
|
<select class="border border-gray-300 rounded px-3 py-1.5 text-sm w-44" name="so_payment_type" required>
|
||||||
|
<option value="">선택</option>
|
||||||
|
<option value="이체" <?= old('so_payment_type') === '이체' ? 'selected' : '' ?>>이체</option>
|
||||||
|
<option value="가상계좌" <?= old('so_payment_type') === '가상계좌' ? 'selected' : '' ?>>가상계좌</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4">
|
||||||
|
<label class="block text-sm font-bold text-gray-700 mb-2">주문 품목</label>
|
||||||
|
<div class="border border-gray-300 overflow-auto">
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="w-16">순번</th>
|
||||||
|
<th>봉투</th>
|
||||||
|
<th class="w-32">수량</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php for ($i = 0; $i < 3; $i++): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><?= $i + 1 ?></td>
|
||||||
|
<td>
|
||||||
|
<select class="border border-gray-300 rounded px-2 py-1 text-sm w-full" name="item_bag_code[]">
|
||||||
|
<option value="">선택</option>
|
||||||
|
<?php foreach ($bagCodes as $cd): ?>
|
||||||
|
<option value="<?= esc($cd->cd_code) ?>">
|
||||||
|
<?= esc($cd->cd_code) ?> — <?= esc($cd->cd_name) ?>
|
||||||
|
</option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input class="border border-gray-300 rounded px-2 py-1 text-sm w-full text-right" name="item_qty[]" type="number" min="0" value="0"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endfor; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 pt-2">
|
||||||
|
<button type="submit" class="bg-btn-search text-white px-6 py-1.5 rounded-sm text-sm shadow hover:opacity-90 transition">등록</button>
|
||||||
|
<a href="<?= base_url('admin/shop-orders') ?>" class="bg-gray-200 text-gray-700 px-6 py-1.5 rounded-sm text-sm hover:bg-gray-300 transition">취소</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
75
app/Views/admin/shop_order/index.php
Normal file
75
app/Views/admin/shop_order/index.php
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<section class="border-b border-gray-300 p-2 shrink-0 bg-control-panel">
|
||||||
|
<div class="flex flex-wrap items-center justify-between gap-y-2">
|
||||||
|
<span class="text-sm font-bold text-gray-700">주문 접수 관리</span>
|
||||||
|
<a href="<?= base_url('admin/shop-orders/create') ?>" class="bg-btn-search text-white px-4 py-1.5 rounded-sm flex items-center gap-1 text-sm shadow hover:opacity-90 transition border border-transparent">주문 접수</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="p-2 bg-white border-b border-gray-200">
|
||||||
|
<form method="GET" action="<?= base_url('admin/shop-orders') ?>" 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>
|
||||||
|
<a href="<?= base_url('admin/shop-orders') ?>" class="text-sm text-gray-500 hover:underline">초기화</a>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<div class="border border-gray-300 overflow-auto mt-2">
|
||||||
|
<table class="w-full data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="w-16">번호</th>
|
||||||
|
<th>판매소</th>
|
||||||
|
<th>접수일</th>
|
||||||
|
<th>배달일</th>
|
||||||
|
<th>결제</th>
|
||||||
|
<th>입금</th>
|
||||||
|
<th>수령</th>
|
||||||
|
<th>수량</th>
|
||||||
|
<th>금액</th>
|
||||||
|
<th class="w-20">상태</th>
|
||||||
|
<th class="w-24">작업</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="text-right">
|
||||||
|
<?php foreach ($list as $row): ?>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><?= esc($row->so_idx) ?></td>
|
||||||
|
<td class="text-left pl-2"><?= esc($row->so_ds_name) ?></td>
|
||||||
|
<td class="text-center"><?= esc($row->so_order_date) ?></td>
|
||||||
|
<td class="text-center"><?= esc($row->so_delivery_date) ?></td>
|
||||||
|
<td class="text-center"><?= esc($row->so_payment_type) ?></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<?php
|
||||||
|
$paidMap = ['0' => '미입금', '1' => '입금'];
|
||||||
|
echo esc($paidMap[$row->so_paid] ?? $row->so_paid);
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<?php
|
||||||
|
$receivedMap = ['0' => '미수령', '1' => '수령'];
|
||||||
|
echo esc($receivedMap[$row->so_received] ?? $row->so_received);
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<td><?= number_format((int) $row->so_total_qty) ?></td>
|
||||||
|
<td><?= number_format((int) $row->so_total_amount) ?></td>
|
||||||
|
<td class="text-center">
|
||||||
|
<?php
|
||||||
|
$statusMap = ['normal' => '정상', 'cancelled' => '취소'];
|
||||||
|
echo esc($statusMap[$row->so_status] ?? $row->so_status);
|
||||||
|
?>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<form action="<?= base_url('admin/shop-orders/cancel/' . (int) $row->so_idx) ?>" method="POST" class="inline" onsubmit="return confirm('취소하시겠습니까?');">
|
||||||
|
<?= csrf_field() ?>
|
||||||
|
<button type="submit" class="text-orange-600 hover:underline text-sm">취소</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
<?php if (empty($list)): ?>
|
||||||
|
<tr><td colspan="11" class="text-center text-gray-400 py-4">등록된 주문이 없습니다.</td></tr>
|
||||||
|
<?php endif; ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
61
e2e/phase4-sales.spec.js
Normal file
61
e2e/phase4-sales.spec.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
// @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('P4: 주문 접수 관리', () => {
|
||||||
|
test('주문 목록 접근', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto('/admin/shop-orders');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/shop-orders/);
|
||||||
|
});
|
||||||
|
test('주문 접수 폼', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto('/admin/shop-orders/create');
|
||||||
|
await expect(page.locator('select[name="so_ds_idx"]')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('P4: 판매/반품 관리', () => {
|
||||||
|
test('판매 목록 접근', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto('/admin/bag-sales');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/bag-sales/);
|
||||||
|
});
|
||||||
|
test('판매 등록 폼', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto('/admin/bag-sales/create');
|
||||||
|
await expect(page.locator('select[name="bs_ds_idx"]')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('P4: 무료용 불출 관리', () => {
|
||||||
|
test('불출 목록 접근', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto('/admin/bag-issues');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/bag-issues/);
|
||||||
|
});
|
||||||
|
test('불출 처리 폼', async ({ page }) => {
|
||||||
|
await loginAsAdmin(page);
|
||||||
|
await page.goto('/admin/bag-issues/create');
|
||||||
|
await expect(page.locator('select[name="bi2_bag_code"]')).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('P4: 지자체관리자 접근', () => {
|
||||||
|
test('주문/판매/불출 접근 가능', async ({ page }) => {
|
||||||
|
await login(page, 'local');
|
||||||
|
await page.goto('/admin/shop-orders');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/shop-orders/);
|
||||||
|
await page.goto('/admin/bag-sales');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/bag-sales/);
|
||||||
|
await page.goto('/admin/bag-issues');
|
||||||
|
await expect(page).toHaveURL(/\/admin\/bag-issues/);
|
||||||
|
});
|
||||||
|
});
|
||||||
81
writable/database/sales_tables.sql
Normal file
81
writable/database/sales_tables.sql
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- 주문/판매/불출 관리 테이블 (Phase 4)
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 주문 접수 (P4-01~03)
|
||||||
|
CREATE TABLE IF NOT EXISTS `shop_order` (
|
||||||
|
`so_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`so_lg_idx` INT UNSIGNED NOT NULL,
|
||||||
|
`so_ds_idx` INT UNSIGNED NULL COMMENT '지정판매소 FK',
|
||||||
|
`so_ds_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '판매소명(스냅샷)',
|
||||||
|
`so_order_date` DATE NOT NULL COMMENT '접수일',
|
||||||
|
`so_delivery_date` DATE NULL COMMENT '배달일',
|
||||||
|
`so_payment_type` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '이체/가상계좌',
|
||||||
|
`so_paid` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '입금여부 1=예',
|
||||||
|
`so_received` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '수령여부 1=예',
|
||||||
|
`so_total_qty` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
`so_total_amount` DECIMAL(14,2) NOT NULL DEFAULT 0,
|
||||||
|
`so_status` VARCHAR(10) NOT NULL DEFAULT 'normal' COMMENT 'normal/cancelled',
|
||||||
|
`so_orderer_idx` INT UNSIGNED NULL,
|
||||||
|
`so_regdate` DATETIME NOT NULL,
|
||||||
|
PRIMARY KEY (`so_idx`),
|
||||||
|
KEY `idx_so_lg_idx` (`so_lg_idx`),
|
||||||
|
KEY `idx_so_ds_idx` (`so_ds_idx`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='주문 접수';
|
||||||
|
|
||||||
|
-- 주문 상세
|
||||||
|
CREATE TABLE IF NOT EXISTS `shop_order_item` (
|
||||||
|
`soi_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`soi_so_idx` INT UNSIGNED NOT NULL,
|
||||||
|
`soi_bag_code` VARCHAR(50) NOT NULL,
|
||||||
|
`soi_bag_name` VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
`soi_unit_price` DECIMAL(12,2) NOT NULL DEFAULT 0,
|
||||||
|
`soi_qty` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '접수량(낱장)',
|
||||||
|
`soi_amount` DECIMAL(14,2) NOT NULL DEFAULT 0,
|
||||||
|
`soi_box_count` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
`soi_pack_count` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
`soi_sheet_count` INT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (`soi_idx`),
|
||||||
|
KEY `idx_soi_so_idx` (`soi_so_idx`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='주문 상세';
|
||||||
|
|
||||||
|
-- 판매 (P4-04~07)
|
||||||
|
CREATE TABLE IF NOT EXISTS `bag_sale` (
|
||||||
|
`bs_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`bs_lg_idx` INT UNSIGNED NOT NULL,
|
||||||
|
`bs_so_idx` INT UNSIGNED NULL COMMENT '주문 FK (NULL=직접판매)',
|
||||||
|
`bs_ds_idx` INT UNSIGNED NULL COMMENT '지정판매소 FK',
|
||||||
|
`bs_ds_name` VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
`bs_sale_date` DATE NOT NULL,
|
||||||
|
`bs_bag_code` VARCHAR(50) NOT NULL,
|
||||||
|
`bs_bag_name` VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
`bs_qty` INT NOT NULL DEFAULT 0 COMMENT '판매수량(낱장, 음수=반품)',
|
||||||
|
`bs_unit_price` DECIMAL(12,2) NOT NULL DEFAULT 0,
|
||||||
|
`bs_amount` DECIMAL(14,2) NOT NULL DEFAULT 0,
|
||||||
|
`bs_type` VARCHAR(20) NOT NULL DEFAULT 'sale' COMMENT 'sale/return/cancel',
|
||||||
|
`bs_regdate` DATETIME NOT NULL,
|
||||||
|
PRIMARY KEY (`bs_idx`),
|
||||||
|
KEY `idx_bs_lg_idx` (`bs_lg_idx`),
|
||||||
|
KEY `idx_bs_ds_idx` (`bs_ds_idx`),
|
||||||
|
KEY `idx_bs_sale_date` (`bs_sale_date`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='판매/반품';
|
||||||
|
|
||||||
|
-- 불출 (P4-08~10)
|
||||||
|
CREATE TABLE IF NOT EXISTS `bag_issue` (
|
||||||
|
`bi2_idx` INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||||
|
`bi2_lg_idx` INT UNSIGNED NOT NULL,
|
||||||
|
`bi2_year` YEAR NOT NULL,
|
||||||
|
`bi2_quarter` TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '분기(1~4)',
|
||||||
|
`bi2_issue_type` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '무료용/공공용',
|
||||||
|
`bi2_issue_date` DATE NOT NULL,
|
||||||
|
`bi2_dest_type` VARCHAR(20) NOT NULL DEFAULT '' COMMENT '불출처 구분(동사무소 등)',
|
||||||
|
`bi2_dest_name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '불출처명',
|
||||||
|
`bi2_bag_code` VARCHAR(50) NOT NULL,
|
||||||
|
`bi2_bag_name` VARCHAR(100) NOT NULL DEFAULT '',
|
||||||
|
`bi2_qty` INT NOT NULL DEFAULT 0 COMMENT '불출수량(낱장, 음수=취소)',
|
||||||
|
`bi2_status` VARCHAR(10) NOT NULL DEFAULT 'normal' COMMENT 'normal/cancelled',
|
||||||
|
`bi2_regdate` DATETIME NOT NULL,
|
||||||
|
PRIMARY KEY (`bi2_idx`),
|
||||||
|
KEY `idx_bi2_lg_idx` (`bi2_lg_idx`),
|
||||||
|
KEY `idx_bi2_date` (`bi2_issue_date`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='무료용 불출';
|
||||||
Reference in New Issue
Block a user