diff --git a/backend/_workspace/apply_migration.py b/backend/_workspace/apply_migration.py new file mode 100644 index 0000000..ba68cd9 --- /dev/null +++ b/backend/_workspace/apply_migration.py @@ -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) diff --git a/backend/_workspace/migration_zone_delivery_areas.sql b/backend/_workspace/migration_zone_delivery_areas.sql new file mode 100644 index 0000000..497f6b2 --- /dev/null +++ b/backend/_workspace/migration_zone_delivery_areas.sql @@ -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; diff --git a/backend/src/main/java/com/zioinfo/mall/config/SecurityConfig.java b/backend/src/main/java/com/zioinfo/mall/config/SecurityConfig.java index 4df2653..4fff1e2 100644 --- a/backend/src/main/java/com/zioinfo/mall/config/SecurityConfig.java +++ b/backend/src/main/java/com/zioinfo/mall/config/SecurityConfig.java @@ -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", diff --git a/backend/src/main/java/com/zioinfo/mall/zone/MallDeliveryArea.java b/backend/src/main/java/com/zioinfo/mall/zone/MallDeliveryArea.java new file mode 100644 index 0000000..d3bea70 --- /dev/null +++ b/backend/src/main/java/com/zioinfo/mall/zone/MallDeliveryArea.java @@ -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; +} diff --git a/backend/src/main/java/com/zioinfo/mall/zone/ZoneController.java b/backend/src/main/java/com/zioinfo/mall/zone/ZoneController.java index 615cb99..8bc2f31 100644 --- a/backend/src/main/java/com/zioinfo/mall/zone/ZoneController.java +++ b/backend/src/main/java/com/zioinfo/mall/zone/ZoneController.java @@ -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> lookup(@RequestParam String zip) { + double[] origin = resolveZipCoords(zip); // null 가능 List zones = mapper.findByZip(zip); List> 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 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> 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 all = storeMapper.findAll(true); + List> ranked = new ArrayList<>(); + for (MallStore s : all) { + if (s.getLat() == null || s.getLng() == null) continue; + double dist = distanceMi(origin, s); + Map 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 out = new LinkedHashMap<>(); + Map 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> areas() { + return ApiResponse.ok(mapper.findAllAreas()); + } + @GetMapping @PreAuthorize("hasAnyRole('ADMIN','MANAGER')") public ApiResponse> 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 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; + } } diff --git a/backend/src/main/java/com/zioinfo/mall/zone/mapper/ZoneMapper.java b/backend/src/main/java/com/zioinfo/mall/zone/mapper/ZoneMapper.java index 797f0b7..a7db341 100644 --- a/backend/src/main/java/com/zioinfo/mall/zone/mapper/ZoneMapper.java +++ b/backend/src/main/java/com/zioinfo/mall/zone/mapper/ZoneMapper.java @@ -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 findAll(); int insert(MallZone z); int delete(@Param("id") Long id); + + // --- 배송지역(Delivery Areas) --- + List findAllAreas(); + MallDeliveryArea findAreaByZip(@Param("zip") String zip); + MallDeliveryArea findAreaByCity(@Param("city") String city); } diff --git a/backend/src/main/resources/db/schema.sql b/backend/src/main/resources/db/schema.sql index ad2a8ca..b2e7113 100644 --- a/backend/src/main/resources/db/schema.sql +++ b/backend/src/main/resources/db/schema.sql @@ -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. 카탈로그 / 상품 / 사이즈 / 옵션 -- --------------------------------------------------------------------------- diff --git a/backend/src/main/resources/mapper/ZoneMapper.xml b/backend/src/main/resources/mapper/ZoneMapper.xml index 7d4c0c6..32537b5 100644 --- a/backend/src/main/resources/mapper/ZoneMapper.xml +++ b/backend/src/main/resources/mapper/ZoneMapper.xml @@ -13,4 +13,15 @@ VALUES (#{storeId}, #{zip}, #{city}, #{state}, COALESCE(#{radiusMi},15.0), COALESCE(#{surgeEligible},TRUE), COALESCE(#{active},TRUE)) DELETE FROM mall_zone WHERE id = #{id} + + + + +