feat(zone): 배송지역 68시+매장좌표+최근접지점 연결
This commit is contained in:
parent
e6d0eaecaf
commit
2bd44b0d18
31
backend/_workspace/apply_migration.py
Normal file
31
backend/_workspace/apply_migration.py
Normal file
@ -0,0 +1,31 @@
|
||||
import paramiko, sys
|
||||
|
||||
SERVER_IP = '101.79.17.164'
|
||||
LOCAL_SQL = r'C:\GUARDiA\workspace\guardia-mall\backend\_workspace\migration_zone_delivery_areas.sql'
|
||||
REMOTE_SQL = '/tmp/mall_migration_zone.sql'
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect(SERVER_IP, username='root', password='1q2w3e!Q', timeout=30)
|
||||
|
||||
sftp = c.open_sftp()
|
||||
sftp.put(LOCAL_SQL, REMOTE_SQL)
|
||||
sftp.close()
|
||||
print('[OK] uploaded migration SQL')
|
||||
|
||||
def run(cmd):
|
||||
stdin, stdout, stderr = c.exec_command(cmd)
|
||||
out = stdout.read().decode('utf-8', 'replace')
|
||||
err = stderr.read().decode('utf-8', 'replace')
|
||||
rc = stdout.channel.recv_exit_status()
|
||||
return rc, out, err
|
||||
|
||||
rc, out, err = run(f'sudo -u postgres psql -d mall_db -v ON_ERROR_STOP=1 -f {REMOTE_SQL}')
|
||||
print('--- psql stdout ---')
|
||||
print(out)
|
||||
if err.strip():
|
||||
print('--- psql stderr ---')
|
||||
print(err)
|
||||
print(f'[exit={rc}]')
|
||||
c.close()
|
||||
sys.exit(rc)
|
||||
125
backend/_workspace/migration_zone_delivery_areas.sql
Normal file
125
backend/_workspace/migration_zone_delivery_areas.sql
Normal file
@ -0,0 +1,125 @@
|
||||
-- =====================================================================
|
||||
-- GUARDiA Mall — 배송지역(68 NJ 시) + 매장 좌표 + 최근접 지점 연결 마이그레이션
|
||||
-- 대상 DB: mall_db | 멱등(ON CONFLICT / IF NOT EXISTS) | mall 전용
|
||||
-- =====================================================================
|
||||
BEGIN;
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- 1. 매장 좌표 — Montvale Florist 실제 위치 반영
|
||||
-- 41°02'26.8"N 74°01'47.5"W = 41.0408, -74.0299 (store id=1)
|
||||
-- ---------------------------------------------------------------------
|
||||
-- lat/lng 컬럼 보장(이미 존재하면 무시)
|
||||
ALTER TABLE mall_store ADD COLUMN IF NOT EXISTS lat NUMERIC(10,6);
|
||||
ALTER TABLE mall_store ADD COLUMN IF NOT EXISTS lng NUMERIC(10,6);
|
||||
|
||||
UPDATE mall_store SET lat=41.040800, lng=-74.029900 WHERE id=1;
|
||||
|
||||
-- 트라이스테이트 확장 매장 합리적 좌표(주변 분산) — 누락/0인 경우만 보정
|
||||
UPDATE mall_store SET lat=40.885900, lng=-74.043400 WHERE id=2 AND (lat IS NULL);
|
||||
UPDATE mall_store SET lat=40.944500, lng=-74.071500 WHERE id=3 AND (lat IS NULL);
|
||||
UPDATE mall_store SET lat=40.850800, lng=-73.970900 WHERE id=4 AND (lat IS NULL);
|
||||
UPDATE mall_store SET lat=40.892900, lng=-73.972700 WHERE id=5 AND (lat IS NULL);
|
||||
UPDATE mall_store SET lat=40.979500, lng=-74.116600 WHERE id=6 AND (lat IS NULL);
|
||||
UPDATE mall_store SET lat=40.940300, lng=-74.131500 WHERE id=7 AND (lat IS NULL);
|
||||
UPDATE mall_store SET lat=40.897700, lng=-74.016000 WHERE id=8 AND (lat IS NULL);
|
||||
UPDATE mall_store SET lat=40.881400, lng=-74.108500 WHERE id=9 AND (lat IS NULL);
|
||||
UPDATE mall_store SET lat=41.090900, lng=-73.917800 WHERE id=10 AND (lat IS NULL);
|
||||
UPDATE mall_store SET lat=41.088600, lng=-74.013700 WHERE id=11 AND (lat IS NULL);
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- 2. 배송지역(Delivery Areas) — Montvale Florist 당일배송 68개 시(NJ)
|
||||
-- 도시 단위(같은 당일배송 권역). 대표 ZIP/좌표 포함 → 최근접 거리 계산 근거.
|
||||
-- ---------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mall_delivery_area (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
store_id BIGINT NOT NULL DEFAULT 1, -- 매핑 매장(Montvale 플래그십=1)
|
||||
city VARCHAR(120) NOT NULL,
|
||||
state VARCHAR(10) NOT NULL DEFAULT 'NJ',
|
||||
zip VARCHAR(10), -- 대표 ZIP
|
||||
lat NUMERIC(10,6), -- 대표 좌표(시청/중심)
|
||||
lng NUMERIC(10,6),
|
||||
same_day BOOLEAN DEFAULT TRUE, -- 동일(당일) 배송 표시
|
||||
active BOOLEAN DEFAULT TRUE,
|
||||
CONSTRAINT uq_mall_delivery_area UNIQUE (city, state)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_mall_delivery_area_city ON mall_delivery_area(city);
|
||||
CREATE INDEX IF NOT EXISTS idx_mall_delivery_area_zip ON mall_delivery_area(zip);
|
||||
|
||||
INSERT INTO mall_delivery_area (store_id, city, state, zip, lat, lng, same_day) VALUES
|
||||
(1,'Allendale','NJ','07401',41.041400,-74.129400,TRUE),
|
||||
(1,'Alpine','NJ','07620',40.957000,-73.930900,TRUE),
|
||||
(1,'Bergenfield','NJ','07621',40.927400,-73.997400,TRUE),
|
||||
(1,'Bogota','NJ','07603',40.875300,-74.029600,TRUE),
|
||||
(1,'Carlstadt','NJ','07072',40.840300,-74.090300,TRUE),
|
||||
(1,'Cliffside Park','NJ','07010',40.821300,-73.987800,TRUE),
|
||||
(1,'Closter','NJ','07624',40.973100,-73.961800,TRUE),
|
||||
(1,'Cresskill','NJ','07626',40.941900,-73.959700,TRUE),
|
||||
(1,'Demarest','NJ','07627',40.957300,-73.962800,TRUE),
|
||||
(1,'Dumont','NJ','07628',40.940500,-73.992700,TRUE),
|
||||
(1,'East Rutherford','NJ','07073',40.836700,-74.097900,TRUE),
|
||||
(1,'Edgewater','NJ','07020',40.827500,-73.974700,TRUE),
|
||||
(1,'Elmwood Park','NJ','07407',40.904100,-74.118500,TRUE),
|
||||
(1,'Emerson','NJ','07630',40.976300,-74.026900,TRUE),
|
||||
(1,'Englewood Cliffs','NJ','07632',40.886000,-73.952200,TRUE),
|
||||
(1,'Englewood','NJ','07631',40.892900,-73.972700,TRUE),
|
||||
(1,'Fair Lawn','NJ','07410',40.940300,-74.131500,TRUE),
|
||||
(1,'Fairview','NJ','07022',40.811900,-74.001100,TRUE),
|
||||
(1,'Fort Lee','NJ','07024',40.850800,-73.970900,TRUE),
|
||||
(1,'Franklin Lakes','NJ','07417',41.013800,-74.207300,TRUE),
|
||||
(1,'Garfield','NJ','07026',40.881400,-74.108500,TRUE),
|
||||
(1,'Glen Rock','NJ','07452',40.962900,-74.132900,TRUE),
|
||||
(1,'Hackensack','NJ','07601',40.885900,-74.043400,TRUE),
|
||||
(1,'Harrington Park','NJ','07640',40.985100,-73.980200,TRUE),
|
||||
(1,'Hasbrouck Heights','NJ','07604',40.858500,-74.075000,TRUE),
|
||||
(1,'Haworth','NJ','07641',40.960600,-73.991200,TRUE),
|
||||
(1,'Hillsdale','NJ','07642',41.000200,-74.040000,TRUE),
|
||||
(1,'Ho-Ho-Kus','NJ','07423',40.998900,-74.103200,TRUE),
|
||||
(1,'Leonia','NJ','07605',40.862400,-73.989900,TRUE),
|
||||
(1,'Little Ferry','NJ','07643',40.851800,-74.040400,TRUE),
|
||||
(1,'Lodi','NJ','07644',40.882600,-74.083100,TRUE),
|
||||
(1,'Lyndhurst','NJ','07071',40.812200,-74.124200,TRUE),
|
||||
(1,'Mahwah','NJ','07430',41.087400,-74.159000,TRUE),
|
||||
(1,'Maywood','NJ','07607',40.902000,-74.061800,TRUE),
|
||||
(1,'Midland Park','NJ','07432',40.989400,-74.142200,TRUE),
|
||||
(1,'Millburn','NJ','07041',40.725800,-74.302900,TRUE),
|
||||
(1,'Montvale','NJ','07645',41.040800,-74.029900,TRUE),
|
||||
(1,'Moonachie','NJ','07074',40.842500,-74.046500,TRUE),
|
||||
(1,'New Milford','NJ','07646',40.934700,-74.019100,TRUE),
|
||||
(1,'North Arlington','NJ','07031',40.788400,-74.133400,TRUE),
|
||||
(1,'Northvale','NJ','07647',41.007400,-73.948300,TRUE),
|
||||
(1,'Norwood','NJ','07648',40.994400,-73.958800,TRUE),
|
||||
(1,'Oakland','NJ','07436',41.029900,-74.265400,TRUE),
|
||||
(1,'Oradell','NJ','07649',40.955700,-74.034000,TRUE),
|
||||
(1,'Palisades Park','NJ','07650',40.848200,-73.997300,TRUE),
|
||||
(1,'Paramus','NJ','07652',40.944500,-74.071500,TRUE),
|
||||
(1,'Park Ridge','NJ','07656',41.038200,-74.041700,TRUE),
|
||||
(1,'Ramsey','NJ','07446',41.057300,-74.141100,TRUE),
|
||||
(1,'Ridgefield Park','NJ','07660',40.857200,-74.022600,TRUE),
|
||||
(1,'Ridgefield','NJ','07657',40.832800,-74.001500,TRUE),
|
||||
(1,'Ridgewood','NJ','07450',40.979500,-74.116600,TRUE),
|
||||
(1,'River Edge','NJ','07661',40.927300,-74.039600,TRUE),
|
||||
(1,'Rochelle Park','NJ','07662',40.906900,-74.077800,TRUE),
|
||||
(1,'Rutherford','NJ','07070',40.826600,-74.106500,TRUE),
|
||||
(1,'Saddle Brook','NJ','07663',40.902300,-74.092700,TRUE),
|
||||
(1,'Saddle River','NJ','07458',41.029900,-74.099500,TRUE),
|
||||
(1,'Secaucus','NJ','07094',40.789000,-74.056500,TRUE),
|
||||
(1,'South Hackensack','NJ','07606',40.861700,-74.050900,TRUE),
|
||||
(1,'Teaneck','NJ','07666',40.897700,-74.016000,TRUE),
|
||||
(1,'Tenafly','NJ','07670',40.925700,-73.962900,TRUE),
|
||||
(1,'Teterboro','NJ','07608',40.857800,-74.060800,TRUE),
|
||||
(1,'Township Of Washington','NJ','07676',40.992000,-74.063000,TRUE),
|
||||
(1,'Waldwick','NJ','07463',41.012000,-74.118600,TRUE),
|
||||
(1,'Wallington','NJ','07057',40.853100,-74.106000,TRUE),
|
||||
(1,'Westwood','NJ','07675',40.991900,-74.032900,TRUE),
|
||||
(1,'Wood Ridge','NJ','07075',40.844700,-74.087500,TRUE),
|
||||
(1,'Woodcliff Lake','NJ','07677',41.022300,-74.062700,TRUE),
|
||||
(1,'Wyckoff','NJ','07481',41.009600,-74.172600,TRUE)
|
||||
ON CONFLICT (city, state) DO UPDATE
|
||||
SET store_id=EXCLUDED.store_id, zip=EXCLUDED.zip, lat=EXCLUDED.lat, lng=EXCLUDED.lng,
|
||||
same_day=EXCLUDED.same_day, active=TRUE;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- 검증
|
||||
SELECT id, name, lat, lng FROM mall_store WHERE id=1;
|
||||
SELECT COUNT(*) AS delivery_area_count FROM mall_delivery_area WHERE active=TRUE;
|
||||
@ -55,8 +55,10 @@ public class SecurityConfig {
|
||||
"/api/mall/review/**", "/api/mall/promotion/**",
|
||||
"/api/mall/store/**", "/api/mall/schedule/availability",
|
||||
"/api/mall/schedule/holidays").permitAll()
|
||||
// ZIP 진입 조회(공개) — 배송 가능 매장 판정
|
||||
.requestMatchers(HttpMethod.GET, "/api/mall/zone/lookup").permitAll()
|
||||
// ZIP 진입 조회 + 최근접 매장 + 배송지역(공개) — 스토어프론트 진입
|
||||
.requestMatchers(HttpMethod.GET, "/api/mall/zone/lookup",
|
||||
"/api/mall/zone/nearest",
|
||||
"/api/mall/zone/areas").permitAll()
|
||||
// 등급 혜택 안내(공개) + 진행중 이벤트/배너(공개 스토어프론트)
|
||||
.requestMatchers(HttpMethod.GET, "/api/mall/loyalty/tiers", "/api/mall/loyalty/tiers/**",
|
||||
"/api/mall/event/ongoing", "/api/mall/event/banners",
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
package com.zioinfo.mall.zone;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/** 배송지역 (mall_delivery_area). 도시 단위 당일배송 권역 + 대표 좌표. */
|
||||
@Data
|
||||
public class MallDeliveryArea {
|
||||
private Long id;
|
||||
private Long storeId;
|
||||
private String city;
|
||||
private String state;
|
||||
private String zip;
|
||||
private BigDecimal lat;
|
||||
private BigDecimal lng;
|
||||
private Boolean sameDay;
|
||||
private Boolean active;
|
||||
}
|
||||
@ -8,14 +8,16 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 배송권역 API — /api/mall/zone.
|
||||
* ZIP 조회(고객 진입 필터, 공개)는 가용 매장과 배송 가능 여부를 판정한다(Floom 패턴).
|
||||
* ZIP/좌표 조회(고객 진입 필터, 공개)는 가용 매장·배송 가능 여부·최근접 매장을 판정한다(Floom 패턴).
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/mall/zone")
|
||||
@ -27,10 +29,12 @@ public class ZoneController {
|
||||
|
||||
/**
|
||||
* ZIP 진입 조회 — 공개. 해당 ZIP을 커버하는 매장 목록과 deliverable 판정.
|
||||
* 응답: { zip, deliverable, stores:[{storeId,storeName,radiusMi}] }
|
||||
* 고객 좌표(ZIP 대표좌표)를 알면 최근접(haversine) 우선 정렬한다.
|
||||
* 응답: { zip, deliverable, stores:[{storeId,storeName,radiusMi,distanceMi}] }
|
||||
*/
|
||||
@GetMapping("/lookup")
|
||||
public ApiResponse<Map<String, Object>> lookup(@RequestParam String zip) {
|
||||
double[] origin = resolveZipCoords(zip); // null 가능
|
||||
List<MallZone> zones = mapper.findByZip(zip);
|
||||
List<Map<String, Object>> stores = new ArrayList<>();
|
||||
for (MallZone z : zones) {
|
||||
@ -43,8 +47,14 @@ public class ZoneController {
|
||||
m.put("radiusMi", z.getRadiusMi());
|
||||
m.put("sameDayCutoff", s.getSameDayCutoff());
|
||||
m.put("timezone", s.getTimezone());
|
||||
m.put("distanceMi", origin != null ? distanceMi(origin, s) : null);
|
||||
stores.add(m);
|
||||
}
|
||||
// 최근접 우선 정렬(거리 미상은 뒤로)
|
||||
stores.sort(Comparator.comparingDouble(m -> {
|
||||
Object d = m.get("distanceMi");
|
||||
return d == null ? Double.MAX_VALUE : ((Number) d).doubleValue();
|
||||
}));
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
out.put("zip", zip);
|
||||
out.put("deliverable", !stores.isEmpty());
|
||||
@ -52,6 +62,67 @@ public class ZoneController {
|
||||
return ApiResponse.ok(out);
|
||||
}
|
||||
|
||||
/**
|
||||
* 최근접 지점 연결 — 공개. 고객 위치(zip 또는 lat/lng) 기준 haversine 최근접 매장 반환.
|
||||
* 응답: { origin:{lat,lng,source}, nearest:{storeId,storeName,distanceMi,deliverable}, stores:[...정렬...] }
|
||||
*/
|
||||
@GetMapping("/nearest")
|
||||
public ApiResponse<Map<String, Object>> nearest(
|
||||
@RequestParam(required = false) String zip,
|
||||
@RequestParam(required = false) Double lat,
|
||||
@RequestParam(required = false) Double lng) {
|
||||
|
||||
double[] origin = null;
|
||||
String source = null;
|
||||
if (lat != null && lng != null) {
|
||||
origin = new double[]{lat, lng};
|
||||
source = "coords";
|
||||
} else if (zip != null && !zip.isBlank()) {
|
||||
origin = resolveZipCoords(zip);
|
||||
source = origin != null ? "zip" : null;
|
||||
}
|
||||
if (origin == null) {
|
||||
return ApiResponse.fail("위치를 확인할 수 없습니다. zip 또는 lat/lng를 제공하세요.");
|
||||
}
|
||||
|
||||
List<MallStore> all = storeMapper.findAll(true);
|
||||
List<Map<String, Object>> ranked = new ArrayList<>();
|
||||
for (MallStore s : all) {
|
||||
if (s.getLat() == null || s.getLng() == null) continue;
|
||||
double dist = distanceMi(origin, s);
|
||||
Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("storeId", s.getId());
|
||||
m.put("storeCode", s.getCode());
|
||||
m.put("storeName", s.getName());
|
||||
m.put("city", s.getCity());
|
||||
m.put("state", s.getState());
|
||||
m.put("distanceMi", round1(dist));
|
||||
m.put("radiusMi", s.getDeliveryRadiusMi());
|
||||
boolean deliverable = s.getDeliveryRadiusMi() != null
|
||||
&& dist <= s.getDeliveryRadiusMi().doubleValue();
|
||||
m.put("deliverable", deliverable);
|
||||
m.put("sameDayCutoff", s.getSameDayCutoff());
|
||||
ranked.add(m);
|
||||
}
|
||||
ranked.sort(Comparator.comparingDouble(m -> ((Number) m.get("distanceMi")).doubleValue()));
|
||||
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
Map<String, Object> originOut = new LinkedHashMap<>();
|
||||
originOut.put("lat", origin[0]);
|
||||
originOut.put("lng", origin[1]);
|
||||
originOut.put("source", source);
|
||||
out.put("origin", originOut);
|
||||
out.put("nearest", ranked.isEmpty() ? null : ranked.get(0));
|
||||
out.put("stores", ranked);
|
||||
return ApiResponse.ok(out);
|
||||
}
|
||||
|
||||
/** 배송지역(당일배송 시 목록) — 공개 스토어프론트 안내용. */
|
||||
@GetMapping("/areas")
|
||||
public ApiResponse<List<MallDeliveryArea>> areas() {
|
||||
return ApiResponse.ok(mapper.findAllAreas());
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
|
||||
public ApiResponse<List<MallZone>> list(@RequestParam(required = false) Long storeId) {
|
||||
@ -71,4 +142,45 @@ public class ZoneController {
|
||||
mapper.delete(id);
|
||||
return ApiResponse.ok("deleted");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// 내부 유틸
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/** ZIP → 대표좌표. 배송지역 → 권역(zone 매장 좌표) 순으로 근사. 미상 시 null. */
|
||||
private double[] resolveZipCoords(String zip) {
|
||||
MallDeliveryArea a = mapper.findAreaByZip(zip);
|
||||
if (a != null && a.getLat() != null && a.getLng() != null) {
|
||||
return new double[]{a.getLat().doubleValue(), a.getLng().doubleValue()};
|
||||
}
|
||||
// zone 매핑된 매장의 좌표로 근사(같은 권역 대표점)
|
||||
List<MallZone> zones = mapper.findByZip(zip);
|
||||
for (MallZone z : zones) {
|
||||
MallStore s = storeMapper.findById(z.getStoreId());
|
||||
if (s != null && s.getLat() != null && s.getLng() != null) {
|
||||
return new double[]{s.getLat().doubleValue(), s.getLng().doubleValue()};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private double distanceMi(double[] origin, MallStore s) {
|
||||
return round1(haversineMi(origin[0], origin[1],
|
||||
s.getLat().doubleValue(), s.getLng().doubleValue()));
|
||||
}
|
||||
|
||||
/** Haversine 거리(마일). */
|
||||
private static double haversineMi(double lat1, double lng1, double lat2, double lng2) {
|
||||
double R = 3958.7613; // 지구 반경(마일)
|
||||
double dLat = Math.toRadians(lat2 - lat1);
|
||||
double dLng = Math.toRadians(lng2 - lng1);
|
||||
double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
|
||||
+ Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
|
||||
* Math.sin(dLng / 2) * Math.sin(dLng / 2);
|
||||
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
}
|
||||
|
||||
private static double round1(double v) {
|
||||
return Math.round(v * 10.0) / 10.0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.zioinfo.mall.zone.mapper;
|
||||
|
||||
import com.zioinfo.mall.zone.MallDeliveryArea;
|
||||
import com.zioinfo.mall.zone.MallZone;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
@ -13,4 +14,9 @@ public interface ZoneMapper {
|
||||
List<MallZone> findAll();
|
||||
int insert(MallZone z);
|
||||
int delete(@Param("id") Long id);
|
||||
|
||||
// --- 배송지역(Delivery Areas) ---
|
||||
List<MallDeliveryArea> findAllAreas();
|
||||
MallDeliveryArea findAreaByZip(@Param("zip") String zip);
|
||||
MallDeliveryArea findAreaByCity(@Param("city") String city);
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ CREATE TABLE IF NOT EXISTS mall_store (
|
||||
-- Montvale 운영시간/마감(당일배송): 월-금 9:00-17:30(마감 13:00=2PM 사이트표기 기준 보수적 1PM), 토 9:00-16:00(마감 12:00), 일 9:00-12:00(마감 10:00)
|
||||
-- (id 명시: manager 계정 store_id=1 정합)
|
||||
INSERT INTO mall_store (id, code, name, city, state, zip, address, phone, timezone, open_time, close_time, same_day_cutoff, delivery_radius_mi, daily_capacity, lat, lng) VALUES
|
||||
(1,'MONTVALE','Montvale Florist','Montvale','NJ','07645','6 Railroad Ave','+1-201-690-6721','America/New_York','09:00','17:30','13:00',15.0,80,41.046700,-74.023100),
|
||||
(1,'MONTVALE','Montvale Florist','Montvale','NJ','07645','6 Railroad Ave','+1-201-690-6721','America/New_York','09:00','17:30','13:00',15.0,80,41.040800,-74.029900),
|
||||
(2,'HACKENSACK','Montvale Florist — Hackensack','Hackensack','NJ','07601','120 Main St','+1-201-555-0102','America/New_York','09:00','17:30','13:00',12.0,60,40.885900,-74.043400),
|
||||
(3,'PARAMUS','Montvale Florist — Paramus','Paramus','NJ','07652','201 Bergen Town Ctr','+1-201-555-0103','America/New_York','09:00','17:30','13:00',14.0,55,40.944500,-74.071500),
|
||||
(4,'FORTLEE','Montvale Florist — Fort Lee','Fort Lee','NJ','07024','1605 Center Ave','+1-201-555-0104','America/New_York','09:00','17:30','13:00',12.0,50,40.850800,-73.970900),
|
||||
@ -131,6 +131,62 @@ INSERT INTO mall_zone (store_id, zip, city, state, radius_mi) VALUES
|
||||
(11,'10977','Spring Valley','NY',15.0),(11,'10956','New City','NY',15.0)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 2b. 배송지역 (Delivery Areas) — Montvale Florist 당일배송 68개 시(NJ)
|
||||
-- 도시 단위(같은 당일배송 권역) + 대표 ZIP/좌표 → 최근접 매장 거리 계산 근거.
|
||||
-- ---------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS mall_delivery_area (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
store_id BIGINT NOT NULL DEFAULT 1, -- 매핑 매장(Montvale 플래그십=1)
|
||||
city VARCHAR(120) NOT NULL,
|
||||
state VARCHAR(10) NOT NULL DEFAULT 'NJ',
|
||||
zip VARCHAR(10), -- 대표 ZIP
|
||||
lat NUMERIC(10,6), lng NUMERIC(10,6), -- 대표 좌표(시 중심)
|
||||
same_day BOOLEAN DEFAULT TRUE, -- 동일(당일) 배송 표시
|
||||
active BOOLEAN DEFAULT TRUE,
|
||||
CONSTRAINT uq_mall_delivery_area UNIQUE (city, state)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_mall_delivery_area_city ON mall_delivery_area(city);
|
||||
CREATE INDEX IF NOT EXISTS idx_mall_delivery_area_zip ON mall_delivery_area(zip);
|
||||
INSERT INTO mall_delivery_area (store_id, city, state, zip, lat, lng, same_day) VALUES
|
||||
(1,'Allendale','NJ','07401',41.041400,-74.129400,TRUE),(1,'Alpine','NJ','07620',40.957000,-73.930900,TRUE),
|
||||
(1,'Bergenfield','NJ','07621',40.927400,-73.997400,TRUE),(1,'Bogota','NJ','07603',40.875300,-74.029600,TRUE),
|
||||
(1,'Carlstadt','NJ','07072',40.840300,-74.090300,TRUE),(1,'Cliffside Park','NJ','07010',40.821300,-73.987800,TRUE),
|
||||
(1,'Closter','NJ','07624',40.973100,-73.961800,TRUE),(1,'Cresskill','NJ','07626',40.941900,-73.959700,TRUE),
|
||||
(1,'Demarest','NJ','07627',40.957300,-73.962800,TRUE),(1,'Dumont','NJ','07628',40.940500,-73.992700,TRUE),
|
||||
(1,'East Rutherford','NJ','07073',40.836700,-74.097900,TRUE),(1,'Edgewater','NJ','07020',40.827500,-73.974700,TRUE),
|
||||
(1,'Elmwood Park','NJ','07407',40.904100,-74.118500,TRUE),(1,'Emerson','NJ','07630',40.976300,-74.026900,TRUE),
|
||||
(1,'Englewood Cliffs','NJ','07632',40.886000,-73.952200,TRUE),(1,'Englewood','NJ','07631',40.892900,-73.972700,TRUE),
|
||||
(1,'Fair Lawn','NJ','07410',40.940300,-74.131500,TRUE),(1,'Fairview','NJ','07022',40.811900,-74.001100,TRUE),
|
||||
(1,'Fort Lee','NJ','07024',40.850800,-73.970900,TRUE),(1,'Franklin Lakes','NJ','07417',41.013800,-74.207300,TRUE),
|
||||
(1,'Garfield','NJ','07026',40.881400,-74.108500,TRUE),(1,'Glen Rock','NJ','07452',40.962900,-74.132900,TRUE),
|
||||
(1,'Hackensack','NJ','07601',40.885900,-74.043400,TRUE),(1,'Harrington Park','NJ','07640',40.985100,-73.980200,TRUE),
|
||||
(1,'Hasbrouck Heights','NJ','07604',40.858500,-74.075000,TRUE),(1,'Haworth','NJ','07641',40.960600,-73.991200,TRUE),
|
||||
(1,'Hillsdale','NJ','07642',41.000200,-74.040000,TRUE),(1,'Ho-Ho-Kus','NJ','07423',40.998900,-74.103200,TRUE),
|
||||
(1,'Leonia','NJ','07605',40.862400,-73.989900,TRUE),(1,'Little Ferry','NJ','07643',40.851800,-74.040400,TRUE),
|
||||
(1,'Lodi','NJ','07644',40.882600,-74.083100,TRUE),(1,'Lyndhurst','NJ','07071',40.812200,-74.124200,TRUE),
|
||||
(1,'Mahwah','NJ','07430',41.087400,-74.159000,TRUE),(1,'Maywood','NJ','07607',40.902000,-74.061800,TRUE),
|
||||
(1,'Midland Park','NJ','07432',40.989400,-74.142200,TRUE),(1,'Millburn','NJ','07041',40.725800,-74.302900,TRUE),
|
||||
(1,'Montvale','NJ','07645',41.040800,-74.029900,TRUE),(1,'Moonachie','NJ','07074',40.842500,-74.046500,TRUE),
|
||||
(1,'New Milford','NJ','07646',40.934700,-74.019100,TRUE),(1,'North Arlington','NJ','07031',40.788400,-74.133400,TRUE),
|
||||
(1,'Northvale','NJ','07647',41.007400,-73.948300,TRUE),(1,'Norwood','NJ','07648',40.994400,-73.958800,TRUE),
|
||||
(1,'Oakland','NJ','07436',41.029900,-74.265400,TRUE),(1,'Oradell','NJ','07649',40.955700,-74.034000,TRUE),
|
||||
(1,'Palisades Park','NJ','07650',40.848200,-73.997300,TRUE),(1,'Paramus','NJ','07652',40.944500,-74.071500,TRUE),
|
||||
(1,'Park Ridge','NJ','07656',41.038200,-74.041700,TRUE),(1,'Ramsey','NJ','07446',41.057300,-74.141100,TRUE),
|
||||
(1,'Ridgefield Park','NJ','07660',40.857200,-74.022600,TRUE),(1,'Ridgefield','NJ','07657',40.832800,-74.001500,TRUE),
|
||||
(1,'Ridgewood','NJ','07450',40.979500,-74.116600,TRUE),(1,'River Edge','NJ','07661',40.927300,-74.039600,TRUE),
|
||||
(1,'Rochelle Park','NJ','07662',40.906900,-74.077800,TRUE),(1,'Rutherford','NJ','07070',40.826600,-74.106500,TRUE),
|
||||
(1,'Saddle Brook','NJ','07663',40.902300,-74.092700,TRUE),(1,'Saddle River','NJ','07458',41.029900,-74.099500,TRUE),
|
||||
(1,'Secaucus','NJ','07094',40.789000,-74.056500,TRUE),(1,'South Hackensack','NJ','07606',40.861700,-74.050900,TRUE),
|
||||
(1,'Teaneck','NJ','07666',40.897700,-74.016000,TRUE),(1,'Tenafly','NJ','07670',40.925700,-73.962900,TRUE),
|
||||
(1,'Teterboro','NJ','07608',40.857800,-74.060800,TRUE),(1,'Township Of Washington','NJ','07676',40.992000,-74.063000,TRUE),
|
||||
(1,'Waldwick','NJ','07463',41.012000,-74.118600,TRUE),(1,'Wallington','NJ','07057',40.853100,-74.106000,TRUE),
|
||||
(1,'Westwood','NJ','07675',40.991900,-74.032900,TRUE),(1,'Wood Ridge','NJ','07075',40.844700,-74.087500,TRUE),
|
||||
(1,'Woodcliff Lake','NJ','07677',41.022300,-74.062700,TRUE),(1,'Wyckoff','NJ','07481',41.009600,-74.172600,TRUE)
|
||||
ON CONFLICT (city, state) DO UPDATE
|
||||
SET store_id=EXCLUDED.store_id, zip=EXCLUDED.zip, lat=EXCLUDED.lat, lng=EXCLUDED.lng,
|
||||
same_day=EXCLUDED.same_day, active=TRUE;
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- 3. 카탈로그 / 상품 / 사이즈 / 옵션
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
@ -13,4 +13,15 @@
|
||||
VALUES (#{storeId}, #{zip}, #{city}, #{state}, COALESCE(#{radiusMi},15.0), COALESCE(#{surgeEligible},TRUE), COALESCE(#{active},TRUE))
|
||||
</insert>
|
||||
<delete id="delete">DELETE FROM mall_zone WHERE id = #{id}</delete>
|
||||
|
||||
<!-- 배송지역(Delivery Areas) — 도시 단위 당일배송 권역 -->
|
||||
<select id="findAllAreas" resultType="com.zioinfo.mall.zone.MallDeliveryArea">
|
||||
SELECT * FROM mall_delivery_area WHERE active = TRUE ORDER BY city
|
||||
</select>
|
||||
<select id="findAreaByZip" resultType="com.zioinfo.mall.zone.MallDeliveryArea">
|
||||
SELECT * FROM mall_delivery_area WHERE zip = #{zip} AND active = TRUE LIMIT 1
|
||||
</select>
|
||||
<select id="findAreaByCity" resultType="com.zioinfo.mall.zone.MallDeliveryArea">
|
||||
SELECT * FROM mall_delivery_area WHERE LOWER(city) = LOWER(#{city}) AND active = TRUE LIMIT 1
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user