diff --git a/app/Config/Routes.php b/app/Config/Routes.php index 49d1a74..64b1ab0 100644 --- a/app/Config/Routes.php +++ b/app/Config/Routes.php @@ -97,6 +97,23 @@ $routes->group('admin', ['filter' => 'adminAuth'], static function ($routes): vo // 재고 현황 (P3-10) $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) $routes->get('packaging-units', 'Admin\PackagingUnit::index'); $routes->get('packaging-units/create', 'Admin\PackagingUnit::create'); diff --git a/app/Controllers/Admin/BagIssue.php b/app/Controllers/Admin/BagIssue.php new file mode 100644 index 0000000..4255041 --- /dev/null +++ b/app/Controllers/Admin/BagIssue.php @@ -0,0 +1,122 @@ +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', '불출이 취소되었습니다.'); + } +} diff --git a/app/Controllers/Admin/BagSale.php b/app/Controllers/Admin/BagSale.php new file mode 100644 index 0000000..8f2b5c2 --- /dev/null +++ b/app/Controllers/Admin/BagSale.php @@ -0,0 +1,114 @@ +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); + } +} diff --git a/app/Controllers/Admin/ShopOrder.php b/app/Controllers/Admin/ShopOrder.php new file mode 100644 index 0000000..fa5eb82 --- /dev/null +++ b/app/Controllers/Admin/ShopOrder.php @@ -0,0 +1,153 @@ +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', '주문이 취소되었습니다.'); + } +} diff --git a/app/Models/BagIssueModel.php b/app/Models/BagIssueModel.php new file mode 100644 index 0000000..855ecc9 --- /dev/null +++ b/app/Models/BagIssueModel.php @@ -0,0 +1,18 @@ + + 무료용 불출 처리 + +
+
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + 취소 +
+
+
diff --git a/app/Views/admin/bag_issue/index.php b/app/Views/admin/bag_issue/index.php new file mode 100644 index 0000000..023e4b7 --- /dev/null +++ b/app/Views/admin/bag_issue/index.php @@ -0,0 +1,60 @@ +
+
+ 무료용 불출 관리 + 불출 처리 +
+
+
+
+ + + + + + 초기화 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
번호연도분기구분불출일불출처봉투코드봉투명수량상태작업
bi2_idx) ?>bi2_year) ?>bi2_quarter) ?>bi2_issue_type) ?>bi2_issue_date) ?>bi2_dest_name) ?>bi2_bag_code) ?>bi2_bag_name) ?>bi2_qty) ?>bi2_status) ?> +
+ + +
+
등록된 불출이 없습니다.
+
diff --git a/app/Views/admin/bag_sale/create.php b/app/Views/admin/bag_sale/create.php new file mode 100644 index 0000000..164366c --- /dev/null +++ b/app/Views/admin/bag_sale/create.php @@ -0,0 +1,56 @@ +
+ 판매 등록 +
+
+
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + 취소 +
+
+
diff --git a/app/Views/admin/bag_sale/index.php b/app/Views/admin/bag_sale/index.php new file mode 100644 index 0000000..83ae634 --- /dev/null +++ b/app/Views/admin/bag_sale/index.php @@ -0,0 +1,63 @@ +
+
+ 판매/반품 관리 + 판매 등록 +
+
+
+
+ + + + + + + + 초기화 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
번호판매소판매일봉투코드봉투명수량단가금액구분
bs_idx) ?>bs_ds_name) ?>bs_sale_date) ?>bs_bag_code) ?>bs_bag_name) ?>bs_qty) ?>bs_unit_price) ?>bs_amount) ?> + '판매', 'return' => '반품', 'cancel' => '취소']; + echo esc($typeMap[$row->bs_type] ?? $row->bs_type); + ?> +
등록된 판매/반품이 없습니다.
+
diff --git a/app/Views/admin/shop_order/create.php b/app/Views/admin/shop_order/create.php new file mode 100644 index 0000000..bf8e74c --- /dev/null +++ b/app/Views/admin/shop_order/create.php @@ -0,0 +1,74 @@ +
+ 주문 접수 +
+
+
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + +
순번봉투수량
+ + + +
+
+
+ +
+ + 취소 +
+
+
diff --git a/app/Views/admin/shop_order/index.php b/app/Views/admin/shop_order/index.php new file mode 100644 index 0000000..f2d4e96 --- /dev/null +++ b/app/Views/admin/shop_order/index.php @@ -0,0 +1,75 @@ +
+
+ 주문 접수 관리 + 주문 접수 +
+
+
+
+ + + + + + 초기화 +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
번호판매소접수일배달일결제입금수령수량금액상태작업
so_idx) ?>so_ds_name) ?>so_order_date) ?>so_delivery_date) ?>so_payment_type) ?> + '미입금', '1' => '입금']; + echo esc($paidMap[$row->so_paid] ?? $row->so_paid); + ?> + + '미수령', '1' => '수령']; + echo esc($receivedMap[$row->so_received] ?? $row->so_received); + ?> + so_total_qty) ?>so_total_amount) ?> + '정상', 'cancelled' => '취소']; + echo esc($statusMap[$row->so_status] ?? $row->so_status); + ?> + +
+ + +
+
등록된 주문이 없습니다.
+
diff --git a/e2e/phase4-sales.spec.js b/e2e/phase4-sales.spec.js new file mode 100644 index 0000000..9da4cdd --- /dev/null +++ b/e2e/phase4-sales.spec.js @@ -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/); + }); +}); diff --git a/writable/database/sales_tables.sql b/writable/database/sales_tables.sql new file mode 100644 index 0000000..7065ba8 --- /dev/null +++ b/writable/database/sales_tables.sql @@ -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='무료용 불출';