Compare commits

...

62 Commits

Author SHA1 Message Date
ahasani 636daca703 fix: improve Redis client shutdown handling to avoid errors 2026-06-17 14:57:52 +03:30
ahasani 9bf294a1f3 refactor: comment out payment validation for invoice amount correction 2026-06-17 10:03:09 +03:30
ahasani 839f6de691 debug for build 2026-06-17 09:07:53 +03:30
ahasani 826041b07a update return from sale invoices jurney and set nama return from sale 2026-06-17 08:50:35 +03:30
ahasani f87e5b9d8e feat: refactor Prisma client initialization and update related services 2026-06-16 15:47:29 +03:30
ahasani 652177862d update config 2026-06-16 10:09:22 +03:30
ahasani ac2e7f5dab feat: comment out CORS configuration in bootstrap function 2026-06-15 21:50:29 +03:30
ahasani f94a108948 feat: update activation expiration handling and enhance payload structure in transaction services 2026-06-15 21:35:53 +03:30
ahasani d51b677f26 feat: update invoice processing to use last_tsp_status for status mapping 2026-06-15 19:34:21 +03:30
ahasani 9170d8cd5a feat: enhance sales invoice processing with correction and return functionalities 2026-06-15 17:14:54 +03:30
ahasani 5f70b95589 feat: refactor sales invoice services and introduce pagination and filtering
- Added SharedSaleInvoicePaginationService for handling pagination logic.
- Introduced SharedSaleInvoiceFilterService to centralize filtering logic for sales invoices.
- Updated SalesInvoicesService to utilize the new pagination and filtering services.
- Refactored findAll methods in SalesInvoicesService, CustomerSaleInvoicesService, and other related services to support pagination and filtering.
- Enhanced DTOs for sales invoice filtering to extend shared filter properties.
- Updated module imports to include new services.
- Cleaned up redundant code related to filtering and pagination across various services.
2026-06-14 16:34:00 +03:30
ahasani d2bd576277 feat(pos): enhance password update process with current password validation 2026-06-11 17:53:08 +03:30
ahasani 23bfe1ecbe Refactor: Remove stored procedures and replace with direct database operations
feat: Add economic code and guild information to sales invoice selection

fix: Update error messages for invoice access and creation to use "صورت‌حساب"

fix: Change error messages in SaleInvoicesService to use "صورت‌حساب" instead of "فاکتور"

fix: Update error messages in SalesInvoicesService for not found cases to use "صورت‌حساب"

fix: Modify TSP service to handle invoice updates and error messages consistently with "صورت‌حساب"

fix: Update common DTO descriptions to refer to "صورت‌حساب" instead of "فاکتور"

fix: Adjust utility functions to handle invoice references and error messages with "صورت‌حساب"
2026-06-11 16:13:17 +03:30
ahasani 47a27fb54f feat: enhance consumer and partner middleware with Redis caching
- Added Redis caching to ConsumerMiddleware and PartnerMiddleware to improve performance by reducing database calls for consumer and partner data.
- Implemented logic to check for cached data before querying the database.
- Updated the InvoicesService to include settlement_type in the selected fields.
- Removed unnecessary console logs from CustomersService.
- Modified SalesInvoicesService to optimize query logic and improve performance.
- Updated StatisticsService to streamline data retrieval and processing.
- Created a new migration to add last_attempt_no and last_tsp_status columns to sales_invoices for better tracking of TSP attempts.
2026-06-09 13:31:58 +03:30
ahasani f61100bf25 feat: enhance CustomerIndividual and CustomerLegal models with new unique input types
feat: add cache invalidation method for POS middleware

feat: implement Redis caching in POS middleware for improved performance

feat: refactor sales invoice DTOs for POS to include correction functionality

feat: extend SalesInvoicesController to handle invoice corrections and returns

feat: update SalesInvoicesService to support invoice correction and return operations

feat: enhance TSP provider correction functionality with new DTOs and utility methods

fix: improve error handling and logging in Nama provider switch adapter

refactor: streamline invoice payload building in TSP provider utilities
2026-06-06 19:53:00 +03:30
ahasani 25e589551b refactor(accounts): rename account DTOs for clarity and consistency 2026-06-05 01:33:30 +03:30
ahasani 5ce560ce97 feat(customers): enhance customer retrieval with filtering options
- Updated `findAll` method in `CustomersController` to accept filtering parameters.
- Implemented filtering logic in `CustomersService` to retrieve customers based on type and search query.
- Added `PosCustomerFilterDto` for query validation.
- Updated customer-related DTOs to make fields optional and added length validation where necessary.
- Modified customer-related logic in `sales-invoice-tsp.utils.ts` to accommodate new DTO structure.
- Added unique constraints to `customer_individuals` and `customer_legal` tables in the database migration.
- Removed commented-out code in `PosMiddleware` for cleaner codebase.
- Set default value for `is_default_guild_good` to false in `OwnedGoodsService`.
2026-06-03 18:00:06 +03:30
ahasani b2d8fdc8a0 some updates 2026-06-01 16:22:35 +03:30
ahasani c11166b365 some update 2026-05-30 19:05:56 +03:30
ahasani 7ae027633b update migrations and create base migration. set discount and tax to sale invoices 2026-05-28 18:27:21 +03:30
ahasani a975f9d02a update migrations and create base migration. set discount and tax to sale invoices 2026-05-28 16:39:07 +03:30
ahasani cd3492d625 update get status of tsp provider 2026-05-27 22:44:01 +03:30
ahasani 816c5ebb50 update switch providers and nama provider. fix original send 2026-05-27 21:55:02 +03:30
ahasani 4836ee4d01 feat: include settlement_type in buildCorrectionPayload and buildPayload functions 2026-05-24 20:16:04 +03:30
ahasani ea6f1bfdd0 feat: add settlement_type to SalesInvoice model and related DTOs
- Added settlement_type field to SalesInvoice model with ENUM values (CASH, CREDIT, MIXED).
- Updated SalesInvoice aggregate types, input types, and where filters to include settlement_type.
- Modified StatisticsService to calculate credit amounts based on settlement_type.
- Enhanced TspProviderOriginalSendPayloadDto to include settlement_type.
- Updated NamaProvider DTOs and utility functions to handle new settlement_type logic.
- Created migration to add settlement_type column to sales_invoices table and backfill existing records.
2026-05-24 19:40:04 +03:30
ahasani b53b7d3ed3 feat(pos): add update password functionality with DTO and service method 2026-05-24 10:44:47 +03:30
ahasani 6f65123816 update enum service 2026-05-23 19:00:25 +03:30
ahasani 2c97b7302d feat(statistics): implement statistics module with controller and service for POS invoices 2026-05-23 18:09:41 +03:30
ahasani 2dc9480170 feat(invoices): add public invoices module with controller and service
refactor: remove unused trigger logs module and related files
refactor: simplify good snapshot handling in sale invoice creation
2026-05-21 21:35:36 +03:30
ahasani 9aa12184a1 refactor: streamline Redis caching logic across services
- Implemented a unified `getAndSet` method in RedisService to handle caching for single, list, and paginated responses.
- Removed redundant cache checks and writes in various services, simplifying the code and improving readability.
- Updated GoodsService, StockKeepingUnitsService, PartnerActivatedLicensesService, and others to utilize the new caching mechanism.
- Adjusted Prisma connection limit for better resource management.
2026-05-21 17:27:37 +03:30
ahasani 1d47fb1a1d refactor(goods): streamline goods service and controller, remove favorites module
- Removed the PosGoodFavorite module, controller, and service to simplify the codebase.
- Updated goods service to handle cache invalidation directly.
- Refactored goods controller to remove commented-out code and improve clarity.
- Introduced OwnedGoods module for better organization of owned goods functionality.
- Updated DTOs to extend from existing structures for consistency.
- Enhanced cache invalidation logic in goods service and owned goods service.
2026-05-20 20:22:00 +03:30
ahasani fc27b9d616 feat(pos): add favorite functionality for goods
- Implemented PosGoodFavoriteController to handle attaching and detaching favorites for goods.
- Created PosGoodFavoriteService to manage the business logic for favorites, including database operations and cache invalidation.
- Added PosGoodFavoriteModule to encapsulate the controller and service.
2026-05-20 11:42:59 +03:30
ahasani 98099e97e7 feat: add field validators for username and password, and integrate them into DTOs 2026-05-19 20:34:05 +03:30
ahasani d526f6ed2c feat: enhance consumer selection to include partner details for individual and legal types 2026-05-19 16:11:59 +03:30
ahasani 62b659246f feat: implement Redis caching for business activities and consumers
- Added Redis caching to BusinessActivitiesService for findAll and findOne methods.
- Integrated Redis caching in BusinessActivityComplexesService for findAll and findOne methods.
- Enhanced ConsumersService with Redis caching for findAll and findOne methods.
- Introduced cache invalidation for partner consumers and business activities.
- Created RedisKeyMaker utility for generating cache keys for consumers, partners, and POS.
- Implemented cache invalidation services for partners and POS.
- Added Redis service methods for JSON handling and key deletion by patterns.
- Updated goods service to include caching and invalidation for goods list.
- Introduced DTO for updating goods.
2026-05-19 15:40:45 +03:30
ahasani c5c522f69c set redis 2026-05-19 09:14:30 +03:30
ahasani 758bb03a26 feat: add update password functionality and DTO for partner service 2026-05-18 13:20:33 +03:30
ahasani 23ae3556de feat: implement consumer info update and password change functionality with DTOs 2026-05-18 10:53:58 +03:30
ahasani 12b11cc238 set vat to sku 2026-05-17 12:04:30 +03:30
ahasani 2d13a8bd9c update 2026-05-16 23:58:59 +03:30
ahasani 2a2c020627 refactor: update Dockerfile cache identifiers and clean up console logs across multiple services 2026-05-16 18:00:08 +03:30
ahasani ba3c544ff8 feat: enhance sale invoices filtering with additional fields and refactor findAll method 2026-05-16 16:12:19 +03:30
ahasani 5baf5bfea6 feat: enhance business activities and sale invoices handling with new query constants and pagination support 2026-05-16 14:49:23 +03:30
ahasani 83e7c26133 update 2026-05-11 20:18:03 +03:30
ahasani 1b26c515c0 update 2026-05-11 20:15:31 +03:30
ahasani 2a4e778c31 feat: enhance authentication flow with device info and optional POS flag in login DTO 2026-05-11 19:09:04 +03:30
ahasani 5e6bd33cdd feat: implement inquiry, send, retry, and revoke actions for sales invoices; enhance query constants and service structure 2026-05-10 19:56:05 +03:30
ahasani 9a76880b1d update dockerfile 2026-05-10 15:15:56 +03:30
ahasani c53eb2dba3 fix: update pnpm install commands to include only-built-dependencies option 2026-05-10 15:07:05 +03:30
ahasani 578e445917 update docker file 2026-05-10 14:56:00 +03:30
ahasani a1e8f40417 feat: enhance sales invoice functionality with inquiry, send, retry, and revoke actions 2026-05-10 14:14:01 +03:30
ahasani afa83895a2 Refactor code structure for improved readability and maintainability 2026-05-10 09:44:49 +03:30
ahasani 4e61ff618e refactor: remove SalesInvoiceItemsService and update sales invoice handling
- Deleted SalesInvoiceItemsService as it was not implemented.
- Changed variable declaration from `const` to `let` in SalesInvoicesService for sales invoice creation.
- Updated response handling after sending to TSP provider in SalesInvoicesService.
- Renamed payload properties in TspProvider DTOs for clarity.
- Enhanced error handling in SalesInvoiceTspSwitchService for unsupported providers.
- Refactored SalesInvoiceTspService to utilize utility functions for payload building and sending.
- Updated PrismaService to increase connection limit and utilize dynamic options.
- Added new migration scripts to update database schema for sale_invoice_tsp_attempts.
- Introduced utility functions for building payloads and handling responses in sales invoice processing.
2026-05-08 18:09:13 +03:30
ahasani fbe7230865 feat: add stock keeping units management
- Created migration to drop `type` column from `stock_keeping_units` table.
- Added `Guild` model to Prisma schema.
- Implemented `BusinessActivitiesQueryService` for querying business activities.
- Developed `GoodsSharedService` for handling goods creation and updates.
- Introduced DTOs for creating and updating stock keeping units.
- Created controller and service for managing stock keeping units.
- Implemented response DTOs for stock keeping units service.
- Established module for stock keeping units management.
2026-05-07 20:30:24 +03:30
ahasani 658496320b feat: implement correction and original send functionality for Nama provider
- Added new DTOs for correction requests and responses in `nama-provider.dto.ts`.
- Updated `nama-provider.adapter.ts` to include `originalSend` and `correctionSend` methods.
- Enhanced `nama-provider.util.ts` with mapping functions for correction requests.
- Created operational guidelines for agents in `AGENT.md`.
- Updated Prisma migrations to support new invoice types and relationships.
- Introduced new service and DTO for creating sales invoices in `sale-invoice-create.service.ts` and `sale-invoice-create.dto.ts`.
- Added utility for handling Prisma errors in `prisma-error.util.ts`.
2026-05-05 22:42:09 +03:30
ahasani 4af07fe3e8 update revoke 2026-05-05 10:14:48 +03:30
ahasani 6a48f203a9 set invoke in nama 2026-05-04 11:21:49 +03:30
ahasani a486127ade feat: implement SalesInvoiceTspSwitchService and SalesInvoiceTspService for handling TSP provider interactions
- Add SalesInvoiceTspSwitchService to manage TSP provider selection and sending invoices.
- Introduce SalesInvoiceTspService for creating, sending, and retrieving sales invoices.
- Implement NamaProviderSwitchAdapter for communication with the NAMA TSP provider API.
- Define DTOs for request and response structures specific to the NAMA provider.
- Enhance error handling and logging for TSP provider interactions.
2026-05-03 16:23:17 +03:30
ahasani ad470d2166 feat: add stock keeping unit management with create, update, and retrieval functionalities
- Implemented CreateStockKeepingUnitDto for creating stock keeping units.
- Added StockKeepingUnitsService for handling business logic related to stock keeping units.
- Created StockKeepingUnitsController to manage HTTP requests for stock keeping units.
- Developed UpdateStockKeepingUnitDto for updating existing stock keeping units.
- Introduced StockKeepingUnitsServiceFindAllResponseDto for response structure.
- Established stock keeping units module for encapsulation of related components.

feat: enhance sales invoice fiscal management with new endpoints

- Created SalesInvoicesFilterDto for filtering sales invoices.
- Implemented PosSalesInvoiceFiscalController for managing fiscal operations on sales invoices.
- Developed PosSalesInvoiceFiscalService to handle fiscal logic and interactions.
- Added methods for sending, retrying, and checking the status of fiscal invoices.
- Integrated error handling and response mapping for fiscal operations.
2026-05-01 19:43:59 +03:30
ahasani a68a7f594d feat: implement tax switch functionality with Nama adapter
- Add DTOs for tax switch operations including payloads and results.
- Create SalesInvoiceFiscalSwitchService to handle sending and retrieving tax data.
- Implement SalesInvoiceFiscalService for managing invoice tax submissions and results persistence.
- Develop NamaTaxSwitchAdapter for interfacing with the external tax service.
- Introduce NamaTaxRequestDto and related classes for structured tax requests.
2026-04-30 16:27:46 +03:30
ahasani 58a7c359d8 feat: add DTOs and services for tax switch integration
- Created SendBulkSaleInvoicesDto for handling bulk sale invoice requests.
- Implemented TaxSwitchSendPayloadDto and related DTOs for tax switch item payloads and results.
- Developed SalesInvoiceTaxSwitchService to manage tax switch operations, including sending and retrieving tax information.
- Added SalesInvoiceTaxService for handling sales invoice tax logic, including bulk sending and persistence of results.
- Introduced NamaTaxSwitchAdapter to interact with the tax switch service, simulating external API responses.
- Created SendBulkSalesInvoicesDto for POS module to handle bulk sales invoice requests.
2026-04-27 22:11:05 +03:30
ahasani dee96b6e91 feat: add response DTOs for various services across modules
- Created response DTOs for ConfigService, AppService, AuthService, CatalogsService, AccountsService, BusinessActivityComplexesService, ComplexPosesService, SalesInvoicesService, BusinessActivitiesService, ConsumerBusinessActivityGoodsService, and others.
- Implemented Create, FindAll, FindOne, and Update response types for services in consumer, partners, and POS modules.
- Added request DTOs for creating and updating goods in the consumer business activities module.
- Introduced filtering DTO for partner licenses.
- Enhanced response mapping for partner license activations.
2026-04-27 10:45:39 +03:30
445 changed files with 38437 additions and 14613 deletions
+5
View File
@@ -16,6 +16,11 @@ PORT="5002"
DB_ROOT_PASSWORD="root_password" DB_ROOT_PASSWORD="root_password"
NODE_ENV="production" NODE_ENV="production"
REDIS_HOST="redis"
REDIS_PORT="6379"
REDIS_DB="0"
REDIS_PASSWORD=""
ARVANCLOUD_ENDPOINT=https://s3.ir-thr-at1.arvanstorage.com ARVANCLOUD_ENDPOINT=https://s3.ir-thr-at1.arvanstorage.com
+11 -3
View File
@@ -11,13 +11,21 @@
"[typescript]": { "[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"editor.formatOnSave": true,
"cSpell.words": [ "cSpell.words": [
"ARVANCLOUD", "ARVANCLOUD",
"autoincrement", "autoincrement",
"Cardex", "Cardex",
"consfee",
"fkey", "fkey",
"iban", "iban",
"MAYKET" "indatim",
] "inno",
"inty",
"irtaxid",
"MAYKET",
"setm",
"spro",
"sstid"
],
"editor.formatOnSave": true
} }
+63
View File
@@ -0,0 +1,63 @@
# AI Agent Specification
## Identity
- Name: `consumer_api_agent`
- Purpose: Assist with development, refactoring, migration, debugging, and data-safe operations for this repository.
- Scope: Entire project rooted at `consumer_api`.
## Core Responsibilities
- Implement and refactor backend features in NestJS modules.
- Keep Prisma schema/migrations consistent and safe.
- Preserve API behavior unless explicitly asked to change it.
- Prefer minimal, focused changes over broad rewrites.
## Working Rules
- Follow repository instructions in `AGENTS.md`.
- Respect existing project conventions (naming, DTO patterns, response mapping).
- Do not revert unrelated user changes.
- Do not run destructive DB commands unless explicitly requested.
- For risky DB operations, perform backup-first workflow.
## Code Standards
- Keep functions small and cohesive.
- Extract reusable logic to utilities/services when complexity grows.
- Validate DTO inputs at boundaries.
- Use typed response DTO aliases where possible.
- Keep Prisma queries explicit with `select` for response control.
## Database Safety
- Never run `prisma migrate reset` on important environments unless explicitly approved.
- Treat applied migration files as immutable history.
- Prefer forward-only migrations.
- For data updates:
- Use transactions.
- Verify row counts before/after.
- Keep operations idempotent when feasible.
## API Patterns
- List endpoints: paginate + summary payload.
- Detail endpoints: full resource shape.
- Enum presentation: use shared translator utility (`translateEnumValue`).
- Fiscal status fallback rule: if no fiscal record, return `NOT_SEND`.
## Execution Checklist
1. Read target module/controller/service/DTO before edits.
2. Apply minimal patch.
3. Run typecheck (`pnpm -s tsc --noEmit`).
4. If data logic changed, verify behavior with targeted checks.
5. Summarize changed files and behavior impact.
## Useful Commands
- Type check:
- `pnpm -s tsc --noEmit`
- Prisma migration status:
- `pnpm prisma migrate status`
- Create migration:
- `pnpm prisma migrate dev --name <name>`
- Apply production migrations:
- `pnpm prisma migrate deploy`
## Communication Style
- Be concise, direct, and implementation-focused.
- Call out assumptions and risky steps before execution.
- Provide concrete next actions after each completed task.
+182
View File
@@ -0,0 +1,182 @@
# AGENTS.md
## Stack
- NestJS
- Prisma
- TypeScript
- pnpm
- RTK enabled
---
# Core Rules
- Keep changes minimal.
- Do not touch unrelated files.
- Prefer existing patterns over new abstractions.
- Preserve API behavior unless requested otherwise.
- Prefer forward Prisma migrations.
- Never edit applied migrations in shared environments.
---
# EXECUTION RULES
- Keep responses short.
- Do not narrate thoughts.
- Do not explain obvious steps.
- Do not create plans for simple tasks.
- Prefer implementation over exploration.
Avoid:
- “I think…”
- “Let me check…”
- “I should inspect…”
- “Im going to…”
---
# RTK Rules
Always prefer RTK commands.
Use:
- `rtk ls`
- `rtk grep`
- `rtk smart`
- `rtk read`
- `rtk git diff`
- `rtk git status`
Avoid raw:
- `cat`
- `grep`
- `rg`
- `git diff`
- recursive repository scans
---
# Reading Strategy
1. `rtk grep`
2. `rtk smart`
3. `rtk read`
Rules:
- Read only necessary files.
- Do not read sibling files unless needed.
- Do not reread unchanged files.
- Stop searching once target location is found.
- Edit quickly after locating target.
Avoid:
- opening entire modules
- multi-file chained reads
- large diff dumps
- exploratory scans
---
# Forbidden Paths
Do not inspect unless required:
- `node_modules/`
- `dist/`
- `coverage/`
- `.prisma/`
- `prisma/migrations/`
---
# Prisma Rules
- Use transactions for multi-step writes.
- Prefer explicit `select/include`.
- Normalize `Decimal` values with `Number(...)`.
- Keep migrations forward-safe.
- Use:
- `pnpm prisma migrate dev --name <name>` for new local migrations
- `pnpm prisma migrate deploy` for applying existing migrations
---
# NestJS Rules
- Keep layering:
- controller
- service
- prisma/shared service
- Reuse shared services.
- Keep DTO validation strict.
- Avoid `any`.
---
# Validation
Typecheck before handoff:
```bash
pnpm -s tsc --noEmit
```
# INVESTIGATION LIMITS
For localized fixes:
- maximum 3 file reads before first edit
- maximum 1 related-file read unless required
- maximum 5 total file reads before implementation
Stop searching once the target service, controller, DTO, schema, or repository method is identified.
# TARGETED READ RULES
Do not read files larger than 300 lines unless required.
For known symbols:
1. rtk grep
2. rtk smart
3. read only the relevant section
Avoid opening entire services, modules, or controllers for localized changes.
Prefer symbol-level inspection.
# REVIEW MODE
During reviews:
- inspect changed files only
- avoid repository-wide searches
- avoid architecture exploration
- avoid reading unrelated modules
Review only code affected by the diff.
# HARD TOKEN LIMITS
For localized fixes:
- Never read more than 5 files before the first edit.
- Never read a file larger than 300 lines unless the target symbol cannot be isolated.
- Never open an entire service, controller, or module when a symbol-level read is possible.
- Never reread a file already summarized by `rtk smart` unless implementation details are required.
When a file exceeds 300 lines:
1. rtk grep
2. rtk smart
3. read only the required section
Avoid full-file reads.
+21 -25
View File
@@ -1,48 +1,44 @@
FROM node:22-slim AS deps FROM node:22-slim AS base
WORKDIR /app WORKDIR /app
ARG NPM_REGISTRY=https://registry.npmjs.org/ ARG NPM_REGISTRY=https://hub.megan.ir/npm/
ENV NPM_CONFIG_REGISTRY=${NPM_REGISTRY} ENV NPM_CONFIG_REGISTRY=${NPM_REGISTRY}
ENV npm_config_registry=${NPM_REGISTRY} ENV npm_config_registry=${NPM_REGISTRY}
ENV PNPM_HOME="/pnpm"
ENV PNPM_STORE_DIR="/pnpm/store"
ENV PATH="${PNPM_HOME}:${PATH}"
ENV COREPACK_NPM_REGISTRY=${NPM_REGISTRY}
ARG PNPM_VERSION=10.17.1
RUN corepack enable RUN npm config set registry ${NPM_REGISTRY} \
RUN npm config set registry ${NPM_REGISTRY} && npm install -g pnpm@${PNPM_VERSION}
FROM base AS build
COPY package.json pnpm-lock.yaml ./ COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile RUN --mount=type=cache,id=pnpm-store-build,sharing=locked,target=/pnpm/store \
pnpm install --frozen-lockfile
FROM node:22-slim AS build
WORKDIR /app
ARG NPM_REGISTRY=https://registry.npmjs.org/
ENV NPM_CONFIG_REGISTRY=${NPM_REGISTRY}
ENV npm_config_registry=${NPM_REGISTRY}
RUN corepack enable
RUN npm config set registry ${NPM_REGISTRY}
COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
ENV PRISMA_CLIENT_ENGINE_TYPE=binary ENV PRISMA_CLIENT_ENGINE_TYPE=binary
RUN pnpm run build
RUN pnpm run build && pnpm prune --prod FROM base AS prod-deps
COPY package.json pnpm-lock.yaml ./
RUN --mount=type=cache,id=pnpm-store-prod,sharing=locked,target=/pnpm/store \
pnpm install --prod --frozen-lockfile \
&& rm -rf /root/.npm /root/.cache
FROM node:22-slim AS runtime FROM node:22-slim AS runtime
WORKDIR /app WORKDIR /app
ARG NPM_REGISTRY=https://registry.npmjs.org/
ENV NPM_CONFIG_REGISTRY=${NPM_REGISTRY}
ENV npm_config_registry=${NPM_REGISTRY}
ENV NODE_ENV=production ENV NODE_ENV=production
COPY --from=build /app/node_modules ./node_modules COPY --from=prod-deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist COPY --from=build /app/dist ./dist
COPY --from=build /app/prisma ./prisma COPY --from=build /app/prisma ./prisma
COPY --from=build /app/package.json ./package.json COPY --from=build /app/package.json ./package.json
USER node
CMD ["node", "dist/src/main.js"] CMD ["node", "dist/src/main.js"]
+1533
View File
File diff suppressed because one or more lines are too long
+74 -5
View File
@@ -15,7 +15,6 @@ services:
- "${DATABASE_PORT}:3306" - "${DATABASE_PORT}:3306"
volumes: volumes:
- db_data:/var/lib/mysql - db_data:/var/lib/mysql
- ./prisma/migrations:/docker-entrypoint-initdb.d
healthcheck: healthcheck:
test: "mysqladmin ping -h 127.0.0.1 -u ${DATABASE_USER} -p'${DB_ROOT_PASSWORD}'" test: "mysqladmin ping -h 127.0.0.1 -u ${DATABASE_USER} -p'${DB_ROOT_PASSWORD}'"
interval: 15s # Shortened interval, as MySQL is starting fast now interval: 15s # Shortened interval, as MySQL is starting fast now
@@ -26,6 +25,24 @@ services:
networks: networks:
- psp_consumer_network - psp_consumer_network
redis:
image: redis:7-alpine
platform: ${DOCKER_PLATFORM:-linux/amd64}
container_name: psp_consumer_api_redis
restart: unless-stopped
command: redis-server --appendonly yes
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 15s
timeout: 5s
retries: 5
networks:
- psp_consumer_network
api: api:
platform: ${DOCKER_PLATFORM:-linux/amd64} platform: ${DOCKER_PLATFORM:-linux/amd64}
build: build:
@@ -52,6 +69,8 @@ services:
depends_on: depends_on:
database: database:
condition: service_healthy condition: service_healthy
redis:
condition: service_healthy
environment: environment:
NODE_ENV: ${NODE_ENV:-production} NODE_ENV: ${NODE_ENV:-production}
PORT: ${PORT} PORT: ${PORT}
@@ -66,12 +85,19 @@ services:
JWT_EXPIRES_IN: ${JWT_EXPIRES_IN} JWT_EXPIRES_IN: ${JWT_EXPIRES_IN}
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:5000,http://127.0.0.1:5000} CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:5000,http://127.0.0.1:5000}
OTP_STATIC_CODE: ${OTP_STATIC_CODE} # From .env OTP_STATIC_CODE: ${OTP_STATIC_CODE} # From .env
REDIS_HOST: ${REDIS_HOST:-redis}
REDIS_PORT: ${REDIS_PORT:-6379}
REDIS_DB: ${REDIS_DB:-0}
REDIS_PASSWORD: ${REDIS_PASSWORD:-}
ports: ports:
- "${PORT:-5002}:5002" - "${PORT:-5002}:5002"
volumes: read_only: true
- ./src:/app/src tmpfs:
- /app/dist - /tmp
- /app/node_modules security_opt:
- no-new-privileges:true
cap_drop:
- ALL
networks: networks:
- psp_consumer_network - psp_consumer_network
# healthcheck: # healthcheck:
@@ -89,9 +115,52 @@ services:
# retries: 3 # retries: 3
# start_period: 10s # start_period: 10s
# seed:
# platform: ${DOCKER_PLATFORM:-linux/amd64}
# build:
# context: .
# dockerfile: Dockerfile
# target: build
# args:
# NODE_ENV: ${NODE_ENV:-production}
# PORT: ${PORT}
# DATABASE_NAME: ${DATABASE_NAME}
# DATABASE_USER: ${DATABASE_USER}
# DATABASE_PASSWORD: ${DATABASE_PASSWORD}
# DATABASE_PORT: ${DATABASE_PORT}
# DATABASE_HOST: ${DATABASE_HOST}
# DATABASE_URL: mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}
# SHADOW_DATABASE_URL: mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}_shadow
# JWT_SECRET: ${JWT_SECRET}
# JWT_EXPIRES_IN: ${JWT_EXPIRES_IN}
# OTP_STATIC_CODE: ${OTP_STATIC_CODE}
# depends_on:
# database:
# condition: service_healthy
# environment:
# NODE_ENV: ${NODE_ENV:-production}
# PORT: ${PORT}
# DATABASE_NAME: ${DATABASE_NAME}
# DATABASE_USER: ${DATABASE_USER}
# DATABASE_PASSWORD: ${DATABASE_PASSWORD}
# DATABASE_PORT: ${DATABASE_PORT}
# DATABASE_HOST: ${DATABASE_HOST}
# DATABASE_URL: mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}
# SHADOW_DATABASE_URL: mysql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}_shadow
# JWT_SECRET: ${JWT_SECRET}
# JWT_EXPIRES_IN: ${JWT_EXPIRES_IN}
# OTP_STATIC_CODE: ${OTP_STATIC_CODE}
# networks:
# - psp_consumer_network
# profiles:
# - tools
# command: ["pnpm", "seed:sku"]
volumes: volumes:
db_data: db_data:
driver: local driver: local
redis_data:
driver: local
networks: networks:
psp_consumer_network: psp_consumer_network:
+5 -1
View File
@@ -9,6 +9,7 @@
"@nestjs/platform-express": "^11.1.19", "@nestjs/platform-express": "^11.1.19",
"@nestjs/swagger": "^11.3.2", "@nestjs/swagger": "^11.3.2",
"@prisma/adapter-mariadb": "^7.7.0", "@prisma/adapter-mariadb": "^7.7.0",
"@prisma/client": "^7.7.0",
"@types/bcrypt": "^6.0.0", "@types/bcrypt": "^6.0.0",
"@types/multer": "^2.1.0", "@types/multer": "^2.1.0",
"bcrypt": "^6.0.0", "bcrypt": "^6.0.0",
@@ -17,6 +18,7 @@
"cookie-parser": "^1.4.7", "cookie-parser": "^1.4.7",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"ioredis": "^5.8.2",
"jalaliday": "^3.1.1", "jalaliday": "^3.1.1",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.3",
"multer": "^2.1.1", "multer": "^2.1.1",
@@ -33,7 +35,6 @@
"@nestjs/cli": "^11.0.21", "@nestjs/cli": "^11.0.21",
"@nestjs/schematics": "^11.1.0", "@nestjs/schematics": "^11.1.0",
"@nestjs/testing": "^11.1.19", "@nestjs/testing": "^11.1.19",
"@prisma/client": "^7.7.0",
"@types/cookie-parser": "^1.4.10", "@types/cookie-parser": "^1.4.10",
"@types/express": "^5.0.6", "@types/express": "^5.0.6",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
@@ -44,6 +45,7 @@
"eslint-plugin-prettier": "^5.5.5", "eslint-plugin-prettier": "^5.5.5",
"globals": "^16.5.0", "globals": "^16.5.0",
"jest": "^30.3.0", "jest": "^30.3.0",
"plop": "^4.0.5",
"prettier": "^3.8.3", "prettier": "^3.8.3",
"prettier-plugin-prisma": "^5.0.0", "prettier-plugin-prisma": "^5.0.0",
"source-map-support": "^0.5.21", "source-map-support": "^0.5.21",
@@ -81,8 +83,10 @@
"db:reset": "tsx scripts/dump-triggers.ts && npx prisma migrate reset --force", "db:reset": "tsx scripts/dump-triggers.ts && npx prisma migrate reset --force",
"dump-triggers": "tsx scripts/dump-triggers.ts", "dump-triggers": "tsx scripts/dump-triggers.ts",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"generate": "plop",
"generate:permissions": "tsx scripts/generate-permissions.ts", "generate:permissions": "tsx scripts/generate-permissions.ts",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"seed:sku": "tsx scripts/seedStockKeepingUnits.ts",
"start": "nest start", "start": "nest start",
"start:debug": "nest start --debug --watch", "start:debug": "nest start --debug --watch",
"start:dev": "nest start --watch", "start:dev": "nest start --watch",
+46
View File
@@ -0,0 +1,46 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
} from '@nestjs/common';
import { {{pascalCase name}}Service } from './{{kebabCase name}}.service';
import { Create{{pascalCase name}}Dto } from './dto/create-{{kebabCase name}}.dto';
import { Update{{pascalCase name}}Dto } from './dto/update-{{kebabCase name}}.dto';
@Controller('{{kebabCase name}}')
export class {{pascalCase name}}Controller {
constructor(private readonly service: {{pascalCase name}}Service) {}
@Get()
findAll() {
return this.service.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.service.findOne(id);
}
@Post()
create(@Body() dto: Create{{pascalCase name}}Dto) {
return this.service.create(dto);
}
@Patch(':id')
update(
@Param('id') id: string,
@Body() dto: Update{{pascalCase name}}Dto,
) {
return this.service.update(id, dto);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.service.remove(id);
}
}
+9
View File
@@ -0,0 +1,9 @@
import { IsString, IsNumber, IsBoolean, IsEmail } from 'class-validator';
export class Create{{pascalCase name}}Dto {
{{#each parsedFields}}
@{{validator}}()
{{name}}: {{type}};
{{/each}}
}
+10
View File
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { {{pascalCase name}}Service } from './{{kebabCase name}}.service';
import { {{pascalCase name}}Controller } from './{{kebabCase name}}.controller';
import { PrismaService } from '{{prismaImportPath modulePath name}}';
@Module({
controllers: [{{pascalCase name}}Controller],
providers: [{{pascalCase name}}Service, PrismaService],
})
export class {{pascalCase name}}Module {}
+63
View File
@@ -0,0 +1,63 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';
import { ResponseMapper } from '@/common/response/response-mapper'
import { Create{{pascalCase name}}Dto } from './dto/create-{{kebabCase name}}.dto';
import { Update{{pascalCase name}}Dto } from './dto/update-{{kebabCase name}}.dto';
@Injectable()
export class {{pascalCase name}}Service {
constructor(private readonly prisma: PrismaService) {}
private readonly summarySelect = {
id: true,
}
private readonly select = {
...this.summarySelect,
}
private readonly where = () => ({})
async findAll() {
const items = await this.prisma.{{camelCase name}}.findMany({
where: this.where(),
select: this.summarySelect,
});
return ResponseMapper.list(items)
}
async findOne(id: string) {
const item = this.prisma.{{camelCase name}}.findUnique({
where: {...this.where(), id },
select: this.select,
});
return ResponseMapper.single(item)
}
async create(createDto: Create{{pascalCase name}}Dto) {
const item = this.prisma.{{camelCase name}}.create({
data: createDto,
select: this.select,
});
return ResponseMapper.create(item)
}
async update(id: string, updateDto: Update{{pascalCase name}}Dto) {
const item = this.prisma.{{camelCase name}}.update({
where: { id },
data: updateDto,
select: this.select,
});
return ResponseMapper.update(item)
}
async remove(id: string) {
return this.prisma.{{camelCase name}}.delete({
where: { id },
});
}
}
+4
View File
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger'
import { Create{{pascalCase name}}Dto } from './create-{{kebabCase name}}.dto';
export class Update{{pascalCase name}}Dto extends PartialType(Create{{pascalCase name}}Dto) {}
+125
View File
@@ -0,0 +1,125 @@
const path = require('path')
module.exports = function (plop) {
// -------------------------
// Case helpers
// -------------------------
const toKebab = str =>
str
.replace(/([a-z])([A-Z])/g, '$1-$2')
.replace(/\s+/g, '-')
.toLowerCase()
const toPascal = str =>
str
.split(/[-_\s]+/)
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
.join('')
const toCamel = str => {
const pascal = toPascal(str)
return pascal.charAt(0).toLowerCase() + pascal.slice(1)
}
plop.setHelper('kebabCase', toKebab)
plop.setHelper('pascalCase', toPascal)
plop.setHelper('camelCase', toCamel)
// -------------------------
// Dynamic relative import helper
// -------------------------
plop.setHelper('prismaImportPath', (modulePath, name) => {
return '@/prisma/prisma.service'
})
// -------------------------
// Generator
// -------------------------
plop.setGenerator('resource', {
description: 'Generate Nested NestJS Prisma Resource',
prompts: [
{
type: 'input',
name: 'name',
message: 'Module name (e.g. user, blog-post):',
},
{
type: 'input',
name: 'modulePath',
message: 'Nested path inside modules (e.g. admin/users) — leave empty for root:',
default: '',
},
{
type: 'input',
name: 'fields',
message:
'DTO fields (comma-separated, e.g. name:string,email:email,age:number,isActive:boolean):',
},
],
actions(data) {
// sanitize path
data.modulePath = data.modulePath.replace(/^\/+|\/+$/g, '')
// Parse fields
const rawFields = data.fields || ''
data.parsedFields = rawFields
.split(',')
.filter(Boolean)
.map(field => {
const [name, type] = field.split(':').map(v => v.trim())
let validator = 'IsString'
let finalType = 'string'
if (type === 'number') {
validator = 'IsNumber'
finalType = 'number'
}
if (type === 'boolean') {
validator = 'IsBoolean'
finalType = 'boolean'
}
if (type === 'email') {
validator = 'IsEmail'
finalType = 'string'
}
return { name, validator, type: finalType }
})
const basePath = data.modulePath
? `src/modules/${data.modulePath}/{{kebabCase name}}`
: `src/modules/{{kebabCase name}}`
return [
{
type: 'add',
path: `${basePath}/{{kebabCase name}}.module.ts`,
templateFile: 'plop-templates/module.hbs',
},
{
type: 'add',
path: `${basePath}/{{kebabCase name}}.service.ts`,
templateFile: 'plop-templates/service.hbs',
},
{
type: 'add',
path: `${basePath}/{{kebabCase name}}.controller.ts`,
templateFile: 'plop-templates/controller.hbs',
},
{
type: 'add',
path: `${basePath}/dto/create-{{kebabCase name}}.dto.ts`,
templateFile: 'plop-templates/create-dto.hbs',
},
{
type: 'add',
path: `${basePath}/dto/update-{{kebabCase name}}.dto.ts`,
templateFile: 'plop-templates/update-dto.hbs',
},
]
},
})
}
+562 -65
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,913 @@
-- CreateTable
CREATE TABLE `admin_accounts` (
`id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`admin_id` VARCHAR(191) NOT NULL,
`account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `admin_accounts_account_id_key`(`account_id`),
INDEX `admin_accounts_admin_id_fkey`(`admin_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `admins` (
`id` VARCHAR(191) NOT NULL,
`mobile_number` VARCHAR(191) NOT NULL,
`national_code` VARCHAR(191) NULL,
`first_name` VARCHAR(191) NOT NULL,
`last_name` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
UNIQUE INDEX `admins_mobile_number_key`(`mobile_number`),
UNIQUE INDEX `admins_national_code_key`(`national_code`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `accounts` (
`id` VARCHAR(191) NOT NULL,
`username` VARCHAR(191) NOT NULL,
`password` VARCHAR(191) NOT NULL,
`status` ENUM('ACTIVE', 'SUSPENDED') NOT NULL,
`type` ENUM('ADMIN', 'PROVIDER', 'PARTNER', 'CONSUMER') NOT NULL,
UNIQUE INDEX `accounts_username_key`(`username`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `device_brands` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `devices` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`os_version` VARCHAR(191) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`brand_id` VARCHAR(191) NOT NULL,
INDEX `devices_brand_id_idx`(`brand_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `license_charged_transactions` (
`id` VARCHAR(191) NOT NULL,
`activation_expires_at` DATETIME(3) NOT NULL,
`tracking_code` VARCHAR(191) NOT NULL,
`purchased_count` INTEGER NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`partner_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `license_charged_transactions_tracking_code_key`(`tracking_code`),
INDEX `license_charged_transactions_partner_id_idx`(`partner_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `licenses` (
`id` VARCHAR(191) NOT NULL,
`accounts_limit` INTEGER NOT NULL DEFAULT 2,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`charge_transaction_id` VARCHAR(191) NOT NULL,
INDEX `licenses_charge_transaction_id_idx`(`charge_transaction_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `licenses_activated` (
`id` VARCHAR(191) NOT NULL,
`starts_at` DATETIME(3) NOT NULL,
`expires_at` DATETIME(3) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`license_id` VARCHAR(191) NOT NULL,
`business_activity_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `licenses_activated_id_key`(`id`),
UNIQUE INDEX `licenses_activated_license_id_key`(`license_id`),
UNIQUE INDEX `licenses_activated_business_activity_id_key`(`business_activity_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `license_renew_charge_transaction` (
`id` VARCHAR(191) NOT NULL,
`activation_expires_at` DATETIME(3) NOT NULL,
`tracking_code` VARCHAR(191) NOT NULL,
`purchased_count` INTEGER NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`partner_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `license_renew_charge_transaction_tracking_code_key`(`tracking_code`),
INDEX `license_renew_charge_transaction_partner_id_idx`(`partner_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `license_renew` (
`id` VARCHAR(191) NOT NULL,
`expires_at` DATETIME(3) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`charge_transaction_id` VARCHAR(191) NOT NULL,
`activation_id` VARCHAR(191) NULL,
INDEX `license_renew_activation_id_idx`(`activation_id`),
INDEX `license_renew_charge_transaction_id_idx`(`charge_transaction_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `partner_account_quota_charge_transaction` (
`id` VARCHAR(191) NOT NULL,
`activation_expires_at` DATETIME(3) NOT NULL,
`tracking_code` VARCHAR(191) NOT NULL,
`purchased_count` INTEGER NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`partner_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `partner_account_quota_charge_transaction_tracking_code_key`(`tracking_code`),
INDEX `partner_account_quota_charge_transaction_partner_id_idx`(`partner_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `partner_account_quota_credit` (
`id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`charge_transaction_id` VARCHAR(191) NULL,
INDEX `partner_account_quota_credit_charge_transaction_id_idx`(`charge_transaction_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `license_account_allocation` (
`id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`license_activation_id` VARCHAR(191) NULL,
`account_id` VARCHAR(191) NULL,
`credit_id` VARCHAR(191) NULL,
UNIQUE INDEX `license_account_allocation_account_id_key`(`account_id`),
UNIQUE INDEX `license_account_allocation_credit_id_key`(`credit_id`),
INDEX `license_account_allocation_license_activation_id_idx`(`license_activation_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `partner_accounts` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('OWNER', 'MANAGER', 'OPERATOR') NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`partner_id` VARCHAR(191) NOT NULL,
`account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `partner_accounts_account_id_key`(`account_id`),
INDEX `partner_accounts_partner_id_fkey`(`partner_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `partners` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`code` VARCHAR(191) NOT NULL,
`status` ENUM('ACTIVE', 'SUSPENDED') NOT NULL DEFAULT 'ACTIVE',
`logo_url` VARCHAR(191) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`tsp_provider` ENUM('NAMA', 'SUN') NOT NULL DEFAULT 'NAMA',
UNIQUE INDEX `partners_code_key`(`code`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `permission_consumers` (
`id` VARCHAR(191) NOT NULL,
`consumer_account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `permission_consumers_consumer_account_id_key`(`consumer_account_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `permission_poses` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('MANAGER', 'OPERATOR') NOT NULL,
`pos_id` VARCHAR(191) NOT NULL,
`permission_id` VARCHAR(191) NOT NULL,
INDEX `permission_poses_pos_id_fkey`(`pos_id`),
UNIQUE INDEX `permission_poses_permission_id_pos_id_key`(`permission_id`, `pos_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `permission_complexes` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('MANAGER', 'OPERATOR') NOT NULL,
`complex_id` VARCHAR(191) NOT NULL,
`permission_id` VARCHAR(191) NOT NULL,
INDEX `permission_complexes_complex_id_fkey`(`complex_id`),
UNIQUE INDEX `permission_complexes_permission_id_complex_id_key`(`permission_id`, `complex_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `permission_business_activities` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('MANAGER', 'OPERATOR') NOT NULL,
`business_id` VARCHAR(191) NOT NULL,
`permission_id` VARCHAR(191) NOT NULL,
INDEX `permission_business_activities_business_id_fkey`(`business_id`),
UNIQUE INDEX `permission_business_activities_permission_id_business_id_key`(`permission_id`, `business_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `provider_accounts` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('OWNER', 'MANAGER', 'OPERATOR') NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`provider_id` VARCHAR(191) NOT NULL,
`account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `provider_accounts_account_id_key`(`account_id`),
INDEX `provider_accounts_provider_id_fkey`(`provider_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `providers` (
`id` VARCHAR(191) NOT NULL,
`code` VARCHAR(191) NOT NULL,
`status` ENUM('ACTIVE', 'SUSPENDED') NOT NULL DEFAULT 'ACTIVE',
`name` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
UNIQUE INDEX `providers_code_key`(`code`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `consumer_account_device` (
`id` VARCHAR(191) NOT NULL,
`device_id` VARCHAR(191) NOT NULL,
`device_name` VARCHAR(191) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`consumer_account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `consumer_account_device_device_id_key`(`device_id`),
UNIQUE INDEX `consumer_account_device_consumer_account_id_key`(`consumer_account_id`),
INDEX `consumer_account_device_consumer_account_id_idx`(`consumer_account_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `application_released_info` (
`id` VARCHAR(191) NOT NULL,
`version` VARCHAR(191) NOT NULL,
`build_number` VARCHAR(191) NOT NULL,
`is_minimum_supported` BOOLEAN NOT NULL DEFAULT false,
`release_type` ENUM('STABLE', 'BETA', 'ALPHA') NOT NULL,
`platform` ENUM('ANDROID', 'IOS') NOT NULL,
`notes` JSON NULL,
`release_date` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `consumer_accounts` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('OWNER', 'MANAGER', 'OPERATOR') NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`consumer_id` VARCHAR(191) NOT NULL,
`account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `consumer_accounts_account_id_key`(`account_id`),
INDEX `consumer_accounts_consumer_id_fkey`(`consumer_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `consumers` (
`id` VARCHAR(191) NOT NULL,
`type` ENUM('INDIVIDUAL', 'LEGAL') NOT NULL,
`status` ENUM('ACTIVE', 'SUSPENDED') NOT NULL DEFAULT 'ACTIVE',
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `consumers_individual` (
`first_name` VARCHAR(191) NOT NULL,
`last_name` VARCHAR(191) NOT NULL,
`mobile_number` VARCHAR(191) NOT NULL,
`national_code` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`partner_id` VARCHAR(191) NOT NULL,
`consumer_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `consumers_individual_mobile_number_consumer_id_key`(`mobile_number`, `consumer_id`),
UNIQUE INDEX `consumers_individual_partner_id_national_code_key`(`partner_id`, `national_code`),
PRIMARY KEY (`consumer_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `consumers_legal` (
`name` VARCHAR(191) NOT NULL,
`registration_code` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`partner_id` VARCHAR(191) NOT NULL,
`consumer_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `consumers_legal_partner_id_registration_code_key`(`partner_id`, `registration_code`),
PRIMARY KEY (`consumer_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `business_activities` (
`id` VARCHAR(191) NOT NULL,
`economic_code` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`fiscal_id` VARCHAR(191) NOT NULL,
`partner_token` VARCHAR(191) NOT NULL,
`invoice_number_sequence` DECIMAL(20, 0) NOT NULL DEFAULT 1,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`guild_id` VARCHAR(191) NOT NULL,
`consumer_id` VARCHAR(191) NOT NULL,
INDEX `business_activities_consumer_id_fkey`(`consumer_id`),
INDEX `business_activities_guild_id_fkey`(`guild_id`),
UNIQUE INDEX `business_activities_economic_code_consumer_id_key`(`economic_code`, `consumer_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `complexes` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`branch_code` VARCHAR(191) NOT NULL,
`address` VARCHAR(191) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`business_activity_id` VARCHAR(191) NOT NULL,
INDEX `complexes_business_activity_id_fkey`(`business_activity_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `poses` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`model` VARCHAR(191) NULL,
`serial_number` VARCHAR(191) NULL,
`status` ENUM('ACTIVE', 'DISABLED') NOT NULL DEFAULT 'ACTIVE',
`pos_type` ENUM('PSP', 'MOBILE', 'WEB', 'API') NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`complex_id` VARCHAR(191) NOT NULL,
`device_id` VARCHAR(191) NULL,
`provider_id` VARCHAR(191) NULL,
`account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `poses_serial_number_key`(`serial_number`),
UNIQUE INDEX `poses_account_id_key`(`account_id`),
INDEX `poses_complex_id_fkey`(`complex_id`),
INDEX `poses_device_id_fkey`(`device_id`),
INDEX `poses_provider_id_fkey`(`provider_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `consumer_account_good_favorites` (
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`consumer_account_id` VARCHAR(191) NOT NULL,
`good_id` VARCHAR(191) NOT NULL,
INDEX `consumer_account_good_favorites_good_id_idx`(`good_id`),
INDEX `consumer_account_good_favorites_consumer_account_id_idx`(`consumer_account_id`),
PRIMARY KEY (`consumer_account_id`, `good_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `goods` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`is_default_guild_good` BOOLEAN NOT NULL DEFAULT false,
`pricing_model` ENUM('STANDARD', 'GOLD') NOT NULL,
`description` TEXT NULL,
`local_sku` VARCHAR(100) NULL,
`barcode` VARCHAR(100) NULL,
`base_sale_price` DECIMAL(15, 0) NULL DEFAULT 0,
`image_url` VARCHAR(255) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`deleted_at` TIMESTAMP(0) NULL,
`sku_id` VARCHAR(191) NOT NULL,
`measure_unit_id` VARCHAR(191) NOT NULL,
`category_id` VARCHAR(191) NULL,
`business_activity_id` VARCHAR(191) NULL,
UNIQUE INDEX `goods_local_sku_key`(`local_sku`),
UNIQUE INDEX `goods_barcode_key`(`barcode`),
INDEX `goods_category_id_idx`(`category_id`),
INDEX `goods_business_activity_id_fkey`(`business_activity_id`),
INDEX `goods_measure_unit_id_fkey`(`measure_unit_id`),
INDEX `goods_sku_id_fkey`(`sku_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `good_categories` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(100) NOT NULL,
`description` TEXT NULL,
`image_url` VARCHAR(255) NULL,
`complex_id` VARCHAR(191) NULL,
`is_default_guild_good` BOOLEAN NOT NULL DEFAULT false,
`guild_id` VARCHAR(191) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`deleted_at` TIMESTAMP(0) NULL,
INDEX `good_categories_complex_id_fkey`(`complex_id`),
INDEX `good_categories_guild_id_fkey`(`guild_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `guilds` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`invoice_template` ENUM('SALE', 'FX_SALE', 'GOLD_JEWELRY', 'CONTRACT', 'UTILITY', 'AIR_TICKET', 'EXPORT', 'BILL_OF_LADING', 'PETROCHEMICAL', 'COMMODITY_EXCHANGE', 'INSURANCE') NOT NULL,
`code` VARCHAR(191) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `measure_units` (
`id` VARCHAR(191) NOT NULL,
`code` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
UNIQUE INDEX `measure_units_code_key`(`code`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `stock_keeping_units` (
`id` VARCHAR(191) NOT NULL,
`code` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`VAT` DECIMAL(5, 2) NOT NULL,
`is_public` BOOLEAN NOT NULL DEFAULT true,
`is_domestic` BOOLEAN NOT NULL DEFAULT false,
`guild_id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
UNIQUE INDEX `stock_keeping_units_code_key`(`code`),
INDEX `stock_keeping_units_code_idx`(`code`),
INDEX `stock_keeping_units_guild_id_fkey`(`guild_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `trigger_logs` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`message` TEXT NOT NULL,
`createdAt` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`name` TEXT NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `customers` (
`id` VARCHAR(191) NOT NULL,
`is_favorite` BOOLEAN NULL DEFAULT false,
`type` ENUM('INDIVIDUAL', 'LEGAL', 'UNKNOWN') NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`deleted_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `customer_individuals` (
`first_name` VARCHAR(255) NOT NULL,
`last_name` VARCHAR(255) NOT NULL,
`national_id` CHAR(10) NOT NULL,
`mobile_number` CHAR(15) NOT NULL,
`postal_code` CHAR(10) NOT NULL,
`economic_code` CHAR(10) NULL,
`customer_id` VARCHAR(191) NOT NULL,
`business_activity_id` VARCHAR(191) NOT NULL,
INDEX `customer_individuals_business_activity_id_idx`(`business_activity_id`),
UNIQUE INDEX `customer_individuals_business_activity_id_national_id_key`(`business_activity_id`, `national_id`),
PRIMARY KEY (`customer_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `customer_legal` (
`name` VARCHAR(255) NOT NULL,
`economic_code` CHAR(10) NOT NULL,
`registration_number` CHAR(20) NULL,
`postal_code` CHAR(10) NOT NULL,
`customer_id` VARCHAR(191) NOT NULL,
`business_activity_id` VARCHAR(191) NOT NULL,
INDEX `customer_legal_business_activity_id_idx`(`business_activity_id`),
UNIQUE INDEX `customer_legal_business_activity_id_economic_code_key`(`business_activity_id`, `economic_code`),
PRIMARY KEY (`customer_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `sales_invoices` (
`id` VARCHAR(191) NOT NULL,
`code` VARCHAR(100) NOT NULL,
`total_amount` DECIMAL(15, 2) NOT NULL,
`invoice_number` INTEGER NOT NULL,
`invoice_date` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`type` ENUM('ORIGINAL', 'CORRECTION', 'REVOKE', 'RETURN') NOT NULL,
`tax_id` VARCHAR(32) NULL,
`notes` TEXT NULL,
`unknown_customer` JSON NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`main_id` VARCHAR(50) NULL,
`ref_id` VARCHAR(50) NULL,
`customer_id` VARCHAR(191) NULL,
`consumer_account_id` VARCHAR(191) NOT NULL,
`pos_id` VARCHAR(191) NOT NULL,
`settlement_type` ENUM('CASH', 'CREDIT', 'MIXED') NOT NULL,
`discount_amount` DECIMAL(15, 2) NULL,
`tax_amount` DECIMAL(15, 2) NULL,
UNIQUE INDEX `sales_invoices_code_key`(`code`),
UNIQUE INDEX `sales_invoices_tax_id_key`(`tax_id`),
UNIQUE INDEX `sales_invoices_ref_id_key`(`ref_id`),
INDEX `sales_invoices_ref_id_idx`(`ref_id`),
INDEX `sales_invoices_tax_id_idx`(`tax_id`),
INDEX `sales_invoices_consumer_account_id_fkey`(`consumer_account_id`),
INDEX `sales_invoices_customer_id_fkey`(`customer_id`),
INDEX `sales_invoices_pos_id_fkey`(`pos_id`),
UNIQUE INDEX `sales_invoices_invoice_number_pos_id_key`(`invoice_number`, `pos_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `sales_invoice_items` (
`id` VARCHAR(191) NOT NULL,
`quantity` DECIMAL(10, 0) NOT NULL,
`measure_unit_text` VARCHAR(50) NOT NULL,
`measure_unit_code` VARCHAR(50) NOT NULL,
`sku_code` VARCHAR(50) NOT NULL,
`sku_vat` DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
`unit_price` DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
`total_amount` DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`discount` DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
`notes` TEXT NULL,
`payload` JSON NULL,
`good_snapshot` JSON NOT NULL,
`invoice_id` VARCHAR(191) NOT NULL,
`good_id` VARCHAR(191) NOT NULL,
`service_id` VARCHAR(191) NULL,
`discount_amount` DECIMAL(15, 2) NULL,
`tax_amount` DECIMAL(15, 2) NULL,
INDEX `sales_invoice_items_invoice_id_good_id_idx`(`invoice_id`, `good_id`),
INDEX `sales_invoice_items_good_id_fkey`(`good_id`),
INDEX `sales_invoice_items_service_id_fkey`(`service_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `sale_invoice_tsp_attempts` (
`id` VARCHAR(191) NOT NULL,
`attempt_no` INTEGER NOT NULL,
`status` ENUM('NOT_SEND', 'QUEUED', 'FISCAL_QUEUED', 'SEND_FAILURE', 'SUCCESS', 'FAILURE') NOT NULL,
`message` TEXT NOT NULL,
`sent_at` TIMESTAMP(0) NULL,
`received_at` TIMESTAMP(0) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`invoice_id` VARCHAR(191) NOT NULL,
`provider_request_payload` JSON NOT NULL,
`raw_request_payload` JSON NOT NULL,
`error_message` TEXT NULL,
`fiscal_warnings` JSON NULL,
`provider_response` JSON NULL,
`validation_errors` JSON NULL,
INDEX `sale_invoice_tsp_attempts_status_idx`(`status`),
INDEX `sale_invoice_tsp_attempts_invoice_id_idx`(`invoice_id`),
UNIQUE INDEX `sale_invoice_tsp_attempts_invoice_id_attempt_no_key`(`invoice_id`, `attempt_no`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `sales_invoice_payments` (
`id` VARCHAR(191) NOT NULL,
`amount` DECIMAL(15, 2) NOT NULL,
`payment_method` ENUM('CHEQUE', 'SET_OFF', 'CASH', 'TERMINAL', 'PAYMENT_GATEWAY', 'CARD', 'BANK', 'OTHER') NOT NULL,
`paid_at` TIMESTAMP(0) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`invoice_id` VARCHAR(191) NOT NULL,
INDEX `sales_invoice_payments_invoice_id_idx`(`invoice_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `sales_invoice_payment_terminal_info` (
`id` VARCHAR(191) NOT NULL,
`terminal_id` VARCHAR(191) NOT NULL,
`stan` VARCHAR(191) NOT NULL,
`rrn` VARCHAR(191) NOT NULL,
`transaction_date_time` DATETIME(3) NOT NULL,
`customer_card_no` VARCHAR(191) NULL,
`description` VARCHAR(191) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`payment_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `sales_invoice_payment_terminal_info_payment_id_key`(`payment_id`),
UNIQUE INDEX `sales_invoice_payment_terminal_info_terminal_id_stan_rrn_pay_key`(`terminal_id`, `stan`, `rrn`, `payment_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `services` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT NULL,
`sku` VARCHAR(100) NOT NULL,
`local_sku` VARCHAR(100) NULL,
`barcode` VARCHAR(100) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`deleted_at` TIMESTAMP(0) NULL,
`category_id` VARCHAR(191) NULL,
`base_sale_price` DECIMAL(15, 0) NOT NULL DEFAULT 0,
`account_id` VARCHAR(191) NOT NULL,
`complex_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `services_sku_key`(`sku`),
UNIQUE INDEX `services_local_sku_key`(`local_sku`),
UNIQUE INDEX `services_barcode_key`(`barcode`),
INDEX `services_category_id_idx`(`category_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `service_categories` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(100) NOT NULL,
`description` TEXT NULL,
`image_url` VARCHAR(255) NULL,
`account_id` VARCHAR(191) NOT NULL,
`complex_id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`deleted_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `admin_accounts` ADD CONSTRAINT `admin_accounts_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `admin_accounts` ADD CONSTRAINT `admin_accounts_admin_id_fkey` FOREIGN KEY (`admin_id`) REFERENCES `admins`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `devices` ADD CONSTRAINT `devices_brand_id_fkey` FOREIGN KEY (`brand_id`) REFERENCES `device_brands`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_charged_transactions` ADD CONSTRAINT `license_charged_transactions_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `licenses` ADD CONSTRAINT `licenses_charge_transaction_id_fkey` FOREIGN KEY (`charge_transaction_id`) REFERENCES `license_charged_transactions`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `licenses_activated` ADD CONSTRAINT `licenses_activated_business_activity_id_fkey` FOREIGN KEY (`business_activity_id`) REFERENCES `business_activities`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `licenses_activated` ADD CONSTRAINT `licenses_activated_license_id_fkey` FOREIGN KEY (`license_id`) REFERENCES `licenses`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_renew_charge_transaction` ADD CONSTRAINT `license_renew_charge_transaction_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_renew` ADD CONSTRAINT `license_renew_activation_id_fkey` FOREIGN KEY (`activation_id`) REFERENCES `licenses_activated`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_renew` ADD CONSTRAINT `license_renew_charge_transaction_id_fkey` FOREIGN KEY (`charge_transaction_id`) REFERENCES `license_renew_charge_transaction`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `partner_account_quota_charge_transaction` ADD CONSTRAINT `partner_account_quota_charge_transaction_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `partner_account_quota_credit` ADD CONSTRAINT `partner_account_quota_credit_charge_transaction_id_fkey` FOREIGN KEY (`charge_transaction_id`) REFERENCES `partner_account_quota_charge_transaction`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_account_allocation` ADD CONSTRAINT `license_account_allocation_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_account_allocation` ADD CONSTRAINT `license_account_allocation_credit_id_fkey` FOREIGN KEY (`credit_id`) REFERENCES `partner_account_quota_credit`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_account_allocation` ADD CONSTRAINT `license_account_allocation_license_activation_id_fkey` FOREIGN KEY (`license_activation_id`) REFERENCES `licenses_activated`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `partner_accounts` ADD CONSTRAINT `partner_accounts_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `partner_accounts` ADD CONSTRAINT `partner_accounts_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_consumers` ADD CONSTRAINT `permission_consumers_consumer_account_id_fkey` FOREIGN KEY (`consumer_account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_poses` ADD CONSTRAINT `permission_poses_permission_id_fkey` FOREIGN KEY (`permission_id`) REFERENCES `permission_consumers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_poses` ADD CONSTRAINT `permission_poses_pos_id_fkey` FOREIGN KEY (`pos_id`) REFERENCES `poses`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_complexes` ADD CONSTRAINT `permission_complexes_complex_id_fkey` FOREIGN KEY (`complex_id`) REFERENCES `complexes`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_complexes` ADD CONSTRAINT `permission_complexes_permission_id_fkey` FOREIGN KEY (`permission_id`) REFERENCES `permission_consumers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_business_activities` ADD CONSTRAINT `permission_business_activities_business_id_fkey` FOREIGN KEY (`business_id`) REFERENCES `business_activities`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_business_activities` ADD CONSTRAINT `permission_business_activities_permission_id_fkey` FOREIGN KEY (`permission_id`) REFERENCES `permission_consumers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `provider_accounts` ADD CONSTRAINT `provider_accounts_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `provider_accounts` ADD CONSTRAINT `provider_accounts_provider_id_fkey` FOREIGN KEY (`provider_id`) REFERENCES `providers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `consumer_account_device` ADD CONSTRAINT `consumer_account_device_consumer_account_id_fkey` FOREIGN KEY (`consumer_account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `consumer_accounts` ADD CONSTRAINT `consumer_accounts_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `consumer_accounts` ADD CONSTRAINT `consumer_accounts_consumer_id_fkey` FOREIGN KEY (`consumer_id`) REFERENCES `consumers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `consumers_individual` ADD CONSTRAINT `consumers_individual_consumer_id_fkey` FOREIGN KEY (`consumer_id`) REFERENCES `consumers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `consumers_individual` ADD CONSTRAINT `consumers_individual_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `consumers_legal` ADD CONSTRAINT `consumers_legal_consumer_id_fkey` FOREIGN KEY (`consumer_id`) REFERENCES `consumers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `consumers_legal` ADD CONSTRAINT `consumers_legal_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `business_activities` ADD CONSTRAINT `business_activities_consumer_id_fkey` FOREIGN KEY (`consumer_id`) REFERENCES `consumers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `business_activities` ADD CONSTRAINT `business_activities_guild_id_fkey` FOREIGN KEY (`guild_id`) REFERENCES `guilds`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `complexes` ADD CONSTRAINT `complexes_business_activity_id_fkey` FOREIGN KEY (`business_activity_id`) REFERENCES `business_activities`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `poses` ADD CONSTRAINT `poses_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `poses` ADD CONSTRAINT `poses_complex_id_fkey` FOREIGN KEY (`complex_id`) REFERENCES `complexes`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `poses` ADD CONSTRAINT `poses_device_id_fkey` FOREIGN KEY (`device_id`) REFERENCES `devices`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `poses` ADD CONSTRAINT `poses_provider_id_fkey` FOREIGN KEY (`provider_id`) REFERENCES `providers`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `consumer_account_good_favorites` ADD CONSTRAINT `consumer_account_good_favorites_consumer_account_id_fkey` FOREIGN KEY (`consumer_account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `consumer_account_good_favorites` ADD CONSTRAINT `consumer_account_good_favorites_good_id_fkey` FOREIGN KEY (`good_id`) REFERENCES `goods`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `goods` ADD CONSTRAINT `goods_business_activity_id_fkey` FOREIGN KEY (`business_activity_id`) REFERENCES `business_activities`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `goods` ADD CONSTRAINT `goods_category_id_fkey` FOREIGN KEY (`category_id`) REFERENCES `good_categories`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `goods` ADD CONSTRAINT `goods_measure_unit_id_fkey` FOREIGN KEY (`measure_unit_id`) REFERENCES `measure_units`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `goods` ADD CONSTRAINT `goods_sku_id_fkey` FOREIGN KEY (`sku_id`) REFERENCES `stock_keeping_units`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `good_categories` ADD CONSTRAINT `good_categories_complex_id_fkey` FOREIGN KEY (`complex_id`) REFERENCES `complexes`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `good_categories` ADD CONSTRAINT `good_categories_guild_id_fkey` FOREIGN KEY (`guild_id`) REFERENCES `guilds`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `stock_keeping_units` ADD CONSTRAINT `stock_keeping_units_guild_id_fkey` FOREIGN KEY (`guild_id`) REFERENCES `guilds`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `customer_individuals` ADD CONSTRAINT `customer_individuals_business_activity_id_fkey` FOREIGN KEY (`business_activity_id`) REFERENCES `business_activities`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `customer_individuals` ADD CONSTRAINT `customer_individuals_customer_id_fkey` FOREIGN KEY (`customer_id`) REFERENCES `customers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `customer_legal` ADD CONSTRAINT `customer_legal_business_activity_id_fkey` FOREIGN KEY (`business_activity_id`) REFERENCES `business_activities`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `customer_legal` ADD CONSTRAINT `customer_legal_customer_id_fkey` FOREIGN KEY (`customer_id`) REFERENCES `customers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoices` ADD CONSTRAINT `sales_invoices_consumer_account_id_fkey` FOREIGN KEY (`consumer_account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoices` ADD CONSTRAINT `sales_invoices_customer_id_fkey` FOREIGN KEY (`customer_id`) REFERENCES `customers`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoices` ADD CONSTRAINT `sales_invoices_pos_id_fkey` FOREIGN KEY (`pos_id`) REFERENCES `poses`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoices` ADD CONSTRAINT `sales_invoices_ref_id_fkey` FOREIGN KEY (`ref_id`) REFERENCES `sales_invoices`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoice_items` ADD CONSTRAINT `sales_invoice_items_good_id_fkey` FOREIGN KEY (`good_id`) REFERENCES `goods`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoice_items` ADD CONSTRAINT `sales_invoice_items_invoice_id_fkey` FOREIGN KEY (`invoice_id`) REFERENCES `sales_invoices`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoice_items` ADD CONSTRAINT `sales_invoice_items_service_id_fkey` FOREIGN KEY (`service_id`) REFERENCES `services`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sale_invoice_tsp_attempts` ADD CONSTRAINT `sale_invoice_tsp_attempts_invoice_id_fkey` FOREIGN KEY (`invoice_id`) REFERENCES `sales_invoices`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoice_payments` ADD CONSTRAINT `sales_invoice_payments_invoice_id_fkey` FOREIGN KEY (`invoice_id`) REFERENCES `sales_invoices`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoice_payment_terminal_info` ADD CONSTRAINT `sales_invoice_payment_terminal_info_payment_id_fkey` FOREIGN KEY (`payment_id`) REFERENCES `sales_invoice_payments`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `services` ADD CONSTRAINT `services_category_id_fkey` FOREIGN KEY (`category_id`) REFERENCES `service_categories`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
@@ -1,571 +0,0 @@
-- CreateTable
CREATE TABLE `admin_accounts` (
`id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`admin_id` VARCHAR(191) NOT NULL,
`account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `admin_accounts_account_id_key`(`account_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `admins` (
`id` VARCHAR(191) NOT NULL,
`mobile_number` VARCHAR(191) NOT NULL,
`national_code` VARCHAR(191) NULL,
`first_name` VARCHAR(191) NOT NULL,
`last_name` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
UNIQUE INDEX `admins_mobile_number_key`(`mobile_number`),
UNIQUE INDEX `admins_national_code_key`(`national_code`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `accounts` (
`id` VARCHAR(191) NOT NULL,
`username` VARCHAR(191) NOT NULL,
`password` VARCHAR(191) NOT NULL,
`status` ENUM('ACTIVE', 'SUSPENDED') NOT NULL,
`type` ENUM('ADMIN', 'PROVIDER', 'PARTNER', 'CONSUMER') NOT NULL,
UNIQUE INDEX `accounts_username_key`(`username`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `consumer_accounts` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('OWNER', 'MANAGER', 'OPERATOR') NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`consumer_id` VARCHAR(191) NOT NULL,
`account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `consumer_accounts_account_id_key`(`account_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `consumers` (
`id` VARCHAR(191) NOT NULL,
`mobile_number` VARCHAR(191) NOT NULL,
`first_name` VARCHAR(191) NOT NULL,
`last_name` VARCHAR(191) NOT NULL,
`status` ENUM('ACTIVE', 'SUSPENDED') NOT NULL DEFAULT 'ACTIVE',
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
UNIQUE INDEX `consumers_mobile_number_key`(`mobile_number`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `business_activities` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`guild_id` VARCHAR(191) NOT NULL,
`consumer_id` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `complexes` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`address` VARCHAR(191) NULL,
`tax_id` VARCHAR(191) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`business_activity_id` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `poses` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`serial` VARCHAR(191) NOT NULL,
`model` VARCHAR(191) NULL,
`status` ENUM('ACTIVE', 'DISABLED') NOT NULL DEFAULT 'ACTIVE',
`pos_type` ENUM('PSP', 'MOBILE', 'WEB', 'API') NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`complex_id` VARCHAR(191) NOT NULL,
`device_id` VARCHAR(191) NULL,
`provider_id` VARCHAR(191) NULL,
UNIQUE INDEX `poses_serial_key`(`serial`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `device_brands` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `devices` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`os_version` VARCHAR(191) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`brand_id` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `licenses` (
`id` VARCHAR(191) NOT NULL,
`starts_at` DATETIME(3) NOT NULL,
`expires_at` DATETIME(3) NOT NULL,
`status` ENUM('ACTIVE', 'EXPIRED', 'SUSPENDED') NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`partner_id` VARCHAR(191) NULL,
`consumer_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `licenses_consumer_id_key`(`consumer_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `partner_accounts` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('OWNER', 'MANAGER', 'OPERATOR') NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`partner_id` VARCHAR(191) NOT NULL,
`account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `partner_accounts_account_id_key`(`account_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `partners` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`code` VARCHAR(191) NOT NULL,
`license_quota` INTEGER NULL DEFAULT 0,
`status` ENUM('ACTIVE', 'SUSPENDED') NOT NULL DEFAULT 'ACTIVE',
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
UNIQUE INDEX `partners_code_key`(`code`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `permission_consumers` (
`id` VARCHAR(191) NOT NULL,
`consumer_account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `permission_consumers_consumer_account_id_key`(`consumer_account_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `permission_poses` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('MANAGER', 'OPERATOR') NOT NULL,
`pos_id` VARCHAR(191) NOT NULL,
`permission_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `permission_poses_permission_id_pos_id_key`(`permission_id`, `pos_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `permission_complexes` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('MANAGER', 'OPERATOR') NOT NULL,
`complex_id` VARCHAR(191) NOT NULL,
`permission_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `permission_complexes_permission_id_complex_id_key`(`permission_id`, `complex_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `permission_business_activities` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('MANAGER', 'OPERATOR') NOT NULL,
`business_id` VARCHAR(191) NOT NULL,
`permission_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `permission_business_activities_permission_id_business_id_key`(`permission_id`, `business_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `provider_accounts` (
`id` VARCHAR(191) NOT NULL,
`role` ENUM('OWNER', 'MANAGER', 'OPERATOR') NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`provider_id` VARCHAR(191) NOT NULL,
`account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `provider_accounts_account_id_key`(`account_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `providers` (
`id` VARCHAR(191) NOT NULL,
`code` VARCHAR(191) NOT NULL,
`status` ENUM('ACTIVE', 'SUSPENDED') NOT NULL DEFAULT 'ACTIVE',
`name` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
UNIQUE INDEX `providers_code_key`(`code`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `trigger_logs` (
`id` INTEGER NOT NULL AUTO_INCREMENT,
`message` TEXT NOT NULL,
`createdAt` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`name` TEXT NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `user_devices` (
`account_id` VARCHAR(255) NULL,
`app_version` VARCHAR(20) NOT NULL,
`build_number` VARCHAR(20) NOT NULL,
`uuid` VARCHAR(255) NOT NULL,
`platform` VARCHAR(100) NOT NULL,
`brand` VARCHAR(100) NOT NULL,
`model` VARCHAR(100) NOT NULL,
`device` VARCHAR(100) NOT NULL,
`os_version` VARCHAR(20) NOT NULL,
`sdk_version` VARCHAR(20) NOT NULL,
`release_number` VARCHAR(20) NOT NULL,
`browser_name` VARCHAR(100) NULL,
`fcm_token` VARCHAR(100) NULL,
UNIQUE INDEX `user_devices_uuid_key`(`uuid`),
PRIMARY KEY (`uuid`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `customers` (
`id` VARCHAR(191) NOT NULL,
`is_favorite` BOOLEAN NULL DEFAULT false,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`deleted_at` TIMESTAMP(0) NULL,
`type` ENUM('INDIVIDUAL', 'LEGAL', 'UNKNOWN') NOT NULL,
`unknown_customer` JSON NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `customer_individuals` (
`first_name` VARCHAR(255) NOT NULL,
`last_name` VARCHAR(255) NOT NULL,
`national_id` CHAR(10) NOT NULL,
`postal_code` CHAR(10) NOT NULL,
`economic_code` CHAR(10) NULL,
`customer_id` VARCHAR(191) NOT NULL,
`complex_id` VARCHAR(191) NOT NULL,
INDEX `customer_individuals_complex_id_idx`(`complex_id`),
UNIQUE INDEX `customer_individuals_complex_id_national_id_key`(`complex_id`, `national_id`),
PRIMARY KEY (`customer_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `customer_legal` (
`company_name` VARCHAR(255) NOT NULL,
`economic_code` CHAR(10) NOT NULL,
`registration_number` CHAR(20) NOT NULL,
`postal_code` CHAR(10) NOT NULL,
`customer_id` VARCHAR(191) NOT NULL,
`complex_id` VARCHAR(191) NOT NULL,
INDEX `customer_legal_complex_id_idx`(`complex_id`),
UNIQUE INDEX `customer_legal_complex_id_registration_number_key`(`complex_id`, `registration_number`),
PRIMARY KEY (`customer_id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `goods` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`is_default_guild_good` BOOLEAN NOT NULL DEFAULT false,
`sku` VARCHAR(100) NOT NULL,
`unit_type` ENUM('COUNT', 'GRAM', 'KILOGRAM', 'MILLILITER', 'LITER', 'METER', 'HOUR') NOT NULL,
`pricing_model` ENUM('STANDARD', 'GOLD') NOT NULL,
`description` TEXT NULL,
`local_sku` VARCHAR(100) NULL,
`barcode` VARCHAR(100) NULL,
`base_sale_price` DECIMAL(15, 0) NULL DEFAULT 0.00,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`deleted_at` TIMESTAMP(0) NULL,
`complex_id` VARCHAR(191) NULL,
`category_id` VARCHAR(191) NULL,
UNIQUE INDEX `goods_local_sku_key`(`local_sku`),
UNIQUE INDEX `goods_barcode_key`(`barcode`),
INDEX `goods_category_id_idx`(`category_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `good_categories` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(100) NOT NULL,
`description` TEXT NULL,
`image_url` VARCHAR(255) NULL,
`complex_id` VARCHAR(191) NULL,
`is_default_guild_good` BOOLEAN NOT NULL DEFAULT false,
`guild_id` VARCHAR(191) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`deleted_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `guilds` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(191) NOT NULL,
`code` VARCHAR(191) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `sales_invoices` (
`id` VARCHAR(191) NOT NULL,
`code` VARCHAR(100) NOT NULL,
`total_amount` DECIMAL(15, 2) NOT NULL,
`notes` TEXT NULL,
`unknown_customer` JSON NULL,
`invoice_date` TIMESTAMP(0) NULL DEFAULT CURRENT_TIMESTAMP(0),
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`customer_id` VARCHAR(191) NULL,
`account_id` VARCHAR(191) NOT NULL,
`pos_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `sales_invoices_code_key`(`code`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `sales_invoice_items` (
`id` VARCHAR(191) NOT NULL,
`quantity` DECIMAL(10, 0) NOT NULL,
`unit_type` ENUM('COUNT', 'GRAM', 'KILOGRAM', 'MILLILITER', 'LITER', 'METER', 'HOUR') NOT NULL,
`unit_price` DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
`total_amount` DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`discount` DECIMAL(15, 2) NOT NULL DEFAULT 0.00,
`notes` TEXT NULL,
`invoice_id` VARCHAR(191) NOT NULL,
`good_id` VARCHAR(191) NULL,
`service_id` VARCHAR(191) NULL,
`payload` JSON NULL,
INDEX `sales_invoice_items_invoice_id_good_id_idx`(`invoice_id`, `good_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `sales_invoice_payments` (
`id` VARCHAR(191) NOT NULL,
`invoice_id` VARCHAR(191) NOT NULL,
`amount` DECIMAL(15, 2) NOT NULL,
`payment_method` ENUM('TERMINAL', 'CASH', 'SET_OFF', 'CARD', 'BANK', 'CHECK', 'OTHER') NOT NULL,
`paid_at` DATETIME(3) NOT NULL,
`created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
INDEX `sales_invoice_payments_invoice_id_idx`(`invoice_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `services` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT NULL,
`sku` VARCHAR(100) NOT NULL,
`local_sku` VARCHAR(100) NULL,
`barcode` VARCHAR(100) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`deleted_at` TIMESTAMP(0) NULL,
`category_id` VARCHAR(191) NULL,
`base_sale_price` DECIMAL(15, 0) NOT NULL DEFAULT 0.00,
`account_id` VARCHAR(191) NOT NULL,
`complex_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `services_sku_key`(`sku`),
UNIQUE INDEX `services_local_sku_key`(`local_sku`),
UNIQUE INDEX `services_barcode_key`(`barcode`),
INDEX `services_category_id_idx`(`category_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `service_categories` (
`id` VARCHAR(191) NOT NULL,
`name` VARCHAR(100) NOT NULL,
`description` TEXT NULL,
`image_url` VARCHAR(255) NULL,
`account_id` VARCHAR(191) NOT NULL,
`complex_id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`deleted_at` TIMESTAMP(0) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `admin_accounts` ADD CONSTRAINT `admin_accounts_admin_id_fkey` FOREIGN KEY (`admin_id`) REFERENCES `admins`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `admin_accounts` ADD CONSTRAINT `admin_accounts_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `consumer_accounts` ADD CONSTRAINT `consumer_accounts_consumer_id_fkey` FOREIGN KEY (`consumer_id`) REFERENCES `consumers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `consumer_accounts` ADD CONSTRAINT `consumer_accounts_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `business_activities` ADD CONSTRAINT `business_activities_guild_id_fkey` FOREIGN KEY (`guild_id`) REFERENCES `guilds`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `business_activities` ADD CONSTRAINT `business_activities_consumer_id_fkey` FOREIGN KEY (`consumer_id`) REFERENCES `consumers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `complexes` ADD CONSTRAINT `complexes_business_activity_id_fkey` FOREIGN KEY (`business_activity_id`) REFERENCES `business_activities`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `poses` ADD CONSTRAINT `poses_complex_id_fkey` FOREIGN KEY (`complex_id`) REFERENCES `complexes`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `poses` ADD CONSTRAINT `poses_device_id_fkey` FOREIGN KEY (`device_id`) REFERENCES `devices`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `poses` ADD CONSTRAINT `poses_provider_id_fkey` FOREIGN KEY (`provider_id`) REFERENCES `providers`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `devices` ADD CONSTRAINT `devices_brand_id_fkey` FOREIGN KEY (`brand_id`) REFERENCES `device_brands`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `licenses` ADD CONSTRAINT `licenses_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `licenses` ADD CONSTRAINT `licenses_consumer_id_fkey` FOREIGN KEY (`consumer_id`) REFERENCES `consumers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `partner_accounts` ADD CONSTRAINT `partner_accounts_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `partner_accounts` ADD CONSTRAINT `partner_accounts_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_consumers` ADD CONSTRAINT `permission_consumers_consumer_account_id_fkey` FOREIGN KEY (`consumer_account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_poses` ADD CONSTRAINT `permission_poses_pos_id_fkey` FOREIGN KEY (`pos_id`) REFERENCES `poses`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_poses` ADD CONSTRAINT `permission_poses_permission_id_fkey` FOREIGN KEY (`permission_id`) REFERENCES `permission_consumers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_complexes` ADD CONSTRAINT `permission_complexes_complex_id_fkey` FOREIGN KEY (`complex_id`) REFERENCES `complexes`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_complexes` ADD CONSTRAINT `permission_complexes_permission_id_fkey` FOREIGN KEY (`permission_id`) REFERENCES `permission_consumers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_business_activities` ADD CONSTRAINT `permission_business_activities_business_id_fkey` FOREIGN KEY (`business_id`) REFERENCES `business_activities`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_business_activities` ADD CONSTRAINT `permission_business_activities_permission_id_fkey` FOREIGN KEY (`permission_id`) REFERENCES `permission_consumers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `provider_accounts` ADD CONSTRAINT `provider_accounts_provider_id_fkey` FOREIGN KEY (`provider_id`) REFERENCES `providers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `provider_accounts` ADD CONSTRAINT `provider_accounts_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `customer_individuals` ADD CONSTRAINT `customer_individuals_customer_id_fkey` FOREIGN KEY (`customer_id`) REFERENCES `customers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `customer_individuals` ADD CONSTRAINT `customer_individuals_complex_id_fkey` FOREIGN KEY (`complex_id`) REFERENCES `complexes`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `customer_legal` ADD CONSTRAINT `customer_legal_customer_id_fkey` FOREIGN KEY (`customer_id`) REFERENCES `customers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `customer_legal` ADD CONSTRAINT `customer_legal_complex_id_fkey` FOREIGN KEY (`complex_id`) REFERENCES `complexes`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `goods` ADD CONSTRAINT `goods_category_id_fkey` FOREIGN KEY (`category_id`) REFERENCES `good_categories`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `goods` ADD CONSTRAINT `goods_complex_id_fkey` FOREIGN KEY (`complex_id`) REFERENCES `complexes`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `good_categories` ADD CONSTRAINT `good_categories_guild_id_fkey` FOREIGN KEY (`guild_id`) REFERENCES `guilds`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `good_categories` ADD CONSTRAINT `good_categories_complex_id_fkey` FOREIGN KEY (`complex_id`) REFERENCES `complexes`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoices` ADD CONSTRAINT `sales_invoices_customer_id_fkey` FOREIGN KEY (`customer_id`) REFERENCES `customers`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoices` ADD CONSTRAINT `sales_invoices_pos_id_fkey` FOREIGN KEY (`pos_id`) REFERENCES `poses`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoices` ADD CONSTRAINT `sales_invoices_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoice_items` ADD CONSTRAINT `sales_invoice_items_invoice_id_fkey` FOREIGN KEY (`invoice_id`) REFERENCES `sales_invoices`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoice_items` ADD CONSTRAINT `sales_invoice_items_good_id_fkey` FOREIGN KEY (`good_id`) REFERENCES `goods`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoice_items` ADD CONSTRAINT `sales_invoice_items_service_id_fkey` FOREIGN KEY (`service_id`) REFERENCES `services`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `sales_invoice_payments` ADD CONSTRAINT `sales_invoice_payments_invoice_id_fkey` FOREIGN KEY (`invoice_id`) REFERENCES `sales_invoices`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `services` ADD CONSTRAINT `services_category_id_fkey` FOREIGN KEY (`category_id`) REFERENCES `service_categories`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
@@ -1,50 +0,0 @@
-- DropForeignKey
ALTER TABLE `permission_business_activities` DROP FOREIGN KEY `permission_business_activities_business_id_fkey`;
-- DropForeignKey
ALTER TABLE `permission_business_activities` DROP FOREIGN KEY `permission_business_activities_permission_id_fkey`;
-- DropForeignKey
ALTER TABLE `permission_complexes` DROP FOREIGN KEY `permission_complexes_complex_id_fkey`;
-- DropForeignKey
ALTER TABLE `permission_complexes` DROP FOREIGN KEY `permission_complexes_permission_id_fkey`;
-- DropForeignKey
ALTER TABLE `permission_consumers` DROP FOREIGN KEY `permission_consumers_consumer_account_id_fkey`;
-- DropForeignKey
ALTER TABLE `permission_poses` DROP FOREIGN KEY `permission_poses_permission_id_fkey`;
-- DropForeignKey
ALTER TABLE `permission_poses` DROP FOREIGN KEY `permission_poses_pos_id_fkey`;
-- DropIndex
DROP INDEX `permission_business_activities_business_id_fkey` ON `permission_business_activities`;
-- DropIndex
DROP INDEX `permission_complexes_complex_id_fkey` ON `permission_complexes`;
-- DropIndex
DROP INDEX `permission_poses_pos_id_fkey` ON `permission_poses`;
-- AddForeignKey
ALTER TABLE `permission_consumers` ADD CONSTRAINT `permission_consumers_consumer_account_id_fkey` FOREIGN KEY (`consumer_account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_poses` ADD CONSTRAINT `permission_poses_pos_id_fkey` FOREIGN KEY (`pos_id`) REFERENCES `poses`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_poses` ADD CONSTRAINT `permission_poses_permission_id_fkey` FOREIGN KEY (`permission_id`) REFERENCES `permission_consumers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_complexes` ADD CONSTRAINT `permission_complexes_complex_id_fkey` FOREIGN KEY (`complex_id`) REFERENCES `complexes`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_complexes` ADD CONSTRAINT `permission_complexes_permission_id_fkey` FOREIGN KEY (`permission_id`) REFERENCES `permission_consumers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_business_activities` ADD CONSTRAINT `permission_business_activities_business_id_fkey` FOREIGN KEY (`business_id`) REFERENCES `business_activities`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `permission_business_activities` ADD CONSTRAINT `permission_business_activities_permission_id_fkey` FOREIGN KEY (`permission_id`) REFERENCES `permission_consumers`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE `goods` ADD COLUMN `image_url` VARCHAR(255) NULL;
@@ -1,78 +0,0 @@
/*
Warnings:
- You are about to drop the column `consumer_id` on the `licenses` table. All the data in the column will be lost.
- You are about to drop the column `expires_at` on the `licenses` table. All the data in the column will be lost.
- You are about to drop the column `starts_at` on the `licenses` table. All the data in the column will be lost.
- You are about to drop the column `status` on the `licenses` table. All the data in the column will be lost.
- A unique constraint covering the columns `[activated_license_id]` on the table `consumers` will be added. If there are existing duplicate values, this will fail.
- Added the required column `activation_expires_at` to the `licenses` table without a default value. This is not possible if the table is not empty.
- Added the required column `charged_license_transaction_id` to the `licenses` table without a default value. This is not possible if the table is not empty.
- Made the column `partner_id` on table `licenses` required. This step will fail if there are existing NULL values in that column.
*/
-- DropForeignKey
ALTER TABLE `licenses` DROP FOREIGN KEY `licenses_consumer_id_fkey`;
-- DropForeignKey
ALTER TABLE `licenses` DROP FOREIGN KEY `licenses_partner_id_fkey`;
-- DropIndex
DROP INDEX `licenses_consumer_id_key` ON `licenses`;
-- DropIndex
DROP INDEX `licenses_partner_id_fkey` ON `licenses`;
-- AlterTable
ALTER TABLE `consumers` ADD COLUMN `activated_license_id` VARCHAR(191) NULL;
-- AlterTable
ALTER TABLE `licenses` DROP COLUMN `consumer_id`,
DROP COLUMN `expires_at`,
DROP COLUMN `starts_at`,
DROP COLUMN `status`,
ADD COLUMN `activation_expires_at` DATETIME(3) NOT NULL,
ADD COLUMN `charged_license_transaction_id` VARCHAR(191) NOT NULL,
MODIFY `partner_id` VARCHAR(191) NOT NULL;
-- CreateTable
CREATE TABLE `activated_licenses` (
`id` VARCHAR(191) NOT NULL,
`starts_at` DATETIME(3) NOT NULL,
`expires_at` DATETIME(3) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`license_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `activated_licenses_id_key`(`id`),
UNIQUE INDEX `activated_licenses_license_id_key`(`license_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `charged_license_transactions` (
`id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`partner_id` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateIndex
CREATE UNIQUE INDEX `consumers_activated_license_id_key` ON `consumers`(`activated_license_id`);
-- AddForeignKey
ALTER TABLE `consumers` ADD CONSTRAINT `consumers_activated_license_id_fkey` FOREIGN KEY (`activated_license_id`) REFERENCES `activated_licenses`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `licenses` ADD CONSTRAINT `licenses_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `licenses` ADD CONSTRAINT `licenses_charged_license_transaction_id_fkey` FOREIGN KEY (`charged_license_transaction_id`) REFERENCES `charged_license_transactions`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `activated_licenses` ADD CONSTRAINT `activated_licenses_license_id_fkey` FOREIGN KEY (`license_id`) REFERENCES `licenses`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `charged_license_transactions` ADD CONSTRAINT `charged_license_transactions_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -1,41 +0,0 @@
/*
Warnings:
- You are about to drop the column `activated_license_id` on the `consumers` table. All the data in the column will be lost.
- You are about to drop the column `activation_expires_at` on the `licenses` table. All the data in the column will be lost.
- You are about to drop the column `partner_id` on the `licenses` table. All the data in the column will be lost.
- You are about to drop the column `license_quota` on the `partners` table. All the data in the column will be lost.
- Added the required column `consumer_id` to the `activated_licenses` table without a default value. This is not possible if the table is not empty.
- Added the required column `activation_expires_at` to the `charged_license_transactions` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE `consumers` DROP FOREIGN KEY `consumers_activated_license_id_fkey`;
-- DropForeignKey
ALTER TABLE `licenses` DROP FOREIGN KEY `licenses_partner_id_fkey`;
-- DropIndex
DROP INDEX `consumers_activated_license_id_key` ON `consumers`;
-- DropIndex
DROP INDEX `licenses_partner_id_fkey` ON `licenses`;
-- AlterTable
ALTER TABLE `activated_licenses` ADD COLUMN `consumer_id` VARCHAR(191) NOT NULL;
-- AlterTable
ALTER TABLE `charged_license_transactions` ADD COLUMN `activation_expires_at` DATETIME(3) NOT NULL;
-- AlterTable
ALTER TABLE `consumers` DROP COLUMN `activated_license_id`;
-- AlterTable
ALTER TABLE `licenses` DROP COLUMN `activation_expires_at`,
DROP COLUMN `partner_id`;
-- AlterTable
ALTER TABLE `partners` DROP COLUMN `license_quota`;
-- AddForeignKey
ALTER TABLE `activated_licenses` ADD CONSTRAINT `activated_licenses_consumer_id_fkey` FOREIGN KEY (`consumer_id`) REFERENCES `consumers`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -1,70 +0,0 @@
/*
Warnings:
- You are about to drop the `user_devices` table. If the table is not empty, all the data it contains will be lost.
- A unique constraint covering the columns `[tracking_code]` on the table `charged_license_transactions` will be added. If there are existing duplicate values, this will fail.
- Added the required column `tracking_code` to the `charged_license_transactions` table without a default value. This is not possible if the table is not empty.
*/
-- DropTable
DROP TABLE `user_devices`;
-- CreateTable
CREATE TABLE `consumer_devices` (
`uuid` VARCHAR(255) NOT NULL,
`app_version` VARCHAR(20) NOT NULL,
`build_number` VARCHAR(20) NOT NULL,
`platform` VARCHAR(100) NOT NULL,
`brand` VARCHAR(100) NOT NULL,
`model` VARCHAR(100) NOT NULL,
`device` VARCHAR(100) NOT NULL,
`os_version` VARCHAR(20) NOT NULL,
`sdk_version` VARCHAR(20) NOT NULL,
`release_number` VARCHAR(20) NOT NULL,
`browser_name` VARCHAR(100) NULL,
`fcm_token` VARCHAR(100) NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`consumer_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `consumer_devices_uuid_key` (`uuid`),
PRIMARY KEY (`uuid`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `application_released_info` (
`id` VARCHAR(191) NOT NULL,
`version` VARCHAR(191) NOT NULL,
`build_number` VARCHAR(191) NOT NULL,
`is_stable` BOOLEAN NOT NULL DEFAULT TRUE,
`is_minimum_supported` BOOLEAN NOT NULL DEFAULT FALSE,
`type` ENUM('ANDROID', 'IOS') NOT NULL,
`notes` JSON NULL,
`release_date` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `consumer_devices`
ADD CONSTRAINT `consumer_devices_consumer_id_fkey` FOREIGN KEY (`consumer_id`) REFERENCES `consumers` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE `charged_license_transactions`
ADD COLUMN `tracking_code` VARCHAR(191) NULL;
UPDATE `charged_license_transactions`
SET
`tracking_code` = CONCAT(
'TRX-',
UPPER(
REPLACE (`id`, '-', '')
)
)
WHERE
`tracking_code` IS NULL
OR `tracking_code` = '';
ALTER TABLE `charged_license_transactions`
MODIFY `tracking_code` VARCHAR(191) NOT NULL;
CREATE UNIQUE INDEX `charged_license_transactions_tracking_code_key` ON `charged_license_transactions` (`tracking_code`);
@@ -1,19 +0,0 @@
/*
Warnings:
- You are about to drop the column `is_stable` on the `application_released_info` table. All the data in the column will be lost.
- You are about to drop the column `type` on the `application_released_info` table. All the data in the column will be lost.
- Added the required column `platform` to the `application_released_info` table without a default value. This is not possible if the table is not empty.
- Added the required column `release_type` to the `application_released_info` table without a default value. This is not possible if the table is not empty.
- Added the required column `publisher` to the `consumer_devices` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE `application_released_info` DROP COLUMN `is_stable`,
DROP COLUMN `type`,
ADD COLUMN `platform` ENUM('ANDROID', 'IOS') NOT NULL,
ADD COLUMN `release_type` ENUM('STABLE', 'BETA', 'ALPHA') NOT NULL;
-- AlterTable
ALTER TABLE `consumer_devices` ADD COLUMN `publisher` ENUM('DIRECT', 'CAFE_BAZAR', 'MAYKET') NOT NULL,
ADD COLUMN `user_agent` VARCHAR(100) NULL;
@@ -1,35 +0,0 @@
/*
Warnings:
- You are about to drop the column `tax_id` on the `complexes` table. All the data in the column will be lost.
- A unique constraint covering the columns `[economic_code]` on the table `business_activities` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[mobile_number,national_code,partner_id]` on the table `consumers` will be added. If there are existing duplicate values, this will fail.
- Added the required column `economic_code` to the `business_activities` table without a default value. This is not possible if the table is not empty.
- Added the required column `national_code` to the `consumers` table without a default value. This is not possible if the table is not empty.
- Added the required column `partner_id` to the `consumers` table without a default value. This is not possible if the table is not empty.
*/
-- DropIndex
DROP INDEX `consumers_mobile_number_key` ON `consumers`;
-- AlterTable
ALTER TABLE `business_activities` ADD COLUMN `economic_code` VARCHAR(191) NOT NULL;
-- AlterTable
ALTER TABLE `complexes` DROP COLUMN `tax_id`;
-- AlterTable
ALTER TABLE `consumers` ADD COLUMN `national_code` VARCHAR(191) NOT NULL,
ADD COLUMN `partner_id` VARCHAR(191) NOT NULL;
-- AlterTable
ALTER TABLE `licenses` ADD COLUMN `accounts_limit` INTEGER NOT NULL DEFAULT 3;
-- CreateIndex
CREATE UNIQUE INDEX `business_activities_economic_code_key` ON `business_activities`(`economic_code`);
-- CreateIndex
CREATE UNIQUE INDEX `consumers_mobile_number_national_code_partner_id_key` ON `consumers`(`mobile_number`, `national_code`, `partner_id`);
-- AddForeignKey
ALTER TABLE `consumers` ADD CONSTRAINT `consumers_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -1,8 +0,0 @@
/*
Warnings:
- Added the required column `branch_code` to the `complexes` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE `complexes` ADD COLUMN `branch_code` VARCHAR(191) NOT NULL;
@@ -1,151 +0,0 @@
/*
Warnings:
- You are about to drop the column `charged_license_transaction_id` on the `licenses` table. All the data in the column will be lost.
- You are about to drop the `activated_licenses` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `charged_license_transactions` table. If the table is not empty, all the data it contains will be lost.
- A unique constraint covering the columns `[mobile_number,partner_id]` on the table `consumers` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[national_code,partner_id]` on the table `consumers` will be added. If there are existing duplicate values, this will fail.
- Added the required column `charge_transaction_id` to the `licenses` table without a default value. This is not possible if the table is not empty.
*/
-- DropForeignKey
ALTER TABLE `activated_licenses` DROP FOREIGN KEY `activated_licenses_consumer_id_fkey`;
-- DropForeignKey
ALTER TABLE `activated_licenses` DROP FOREIGN KEY `activated_licenses_license_id_fkey`;
-- DropForeignKey
ALTER TABLE `charged_license_transactions` DROP FOREIGN KEY `charged_license_transactions_partner_id_fkey`;
-- DropForeignKey
ALTER TABLE `licenses` DROP FOREIGN KEY `licenses_charged_license_transaction_id_fkey`;
-- DropIndex
DROP INDEX `consumers_mobile_number_national_code_partner_id_key` ON `consumers`;
-- DropIndex
DROP INDEX `licenses_charged_license_transaction_id_fkey` ON `licenses`;
-- AlterTable
ALTER TABLE `licenses` DROP COLUMN `charged_license_transaction_id`,
ADD COLUMN `charge_transaction_id` VARCHAR(191) NOT NULL;
-- DropTable
DROP TABLE `activated_licenses`;
-- DropTable
DROP TABLE `charged_license_transactions`;
-- CreateTable
CREATE TABLE `licenses_activated` (
`id` VARCHAR(191) NOT NULL,
`starts_at` DATETIME(3) NOT NULL,
`expires_at` DATETIME(3) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`license_id` VARCHAR(191) NOT NULL,
`business_activity_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `licenses_activated_id_key`(`id`),
UNIQUE INDEX `licenses_activated_license_id_key`(`license_id`),
UNIQUE INDEX `licenses_activated_business_activity_id_key`(`business_activity_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `license_charged_transactions` (
`id` VARCHAR(191) NOT NULL,
`activation_expires_at` DATETIME(3) NOT NULL,
`tracking_code` VARCHAR(191) NOT NULL,
`purchased_count` INTEGER NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`partner_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `license_charged_transactions_tracking_code_key`(`tracking_code`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `license_renew` (
`id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`charge_transaction_id` VARCHAR(191) NOT NULL,
`activation_id` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `license_renew_charge_transaction` (
`id` VARCHAR(191) NOT NULL,
`activation_expires_at` DATETIME(3) NOT NULL,
`tracking_code` VARCHAR(191) NOT NULL,
`purchased_count` INTEGER NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`partner_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `license_renew_charge_transaction_tracking_code_key`(`tracking_code`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `partner_account_quota_charge_transaction` (
`id` VARCHAR(191) NOT NULL,
`purchased_count` INTEGER NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`partner_id` VARCHAR(191) NOT NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `partner_account_quota_allocation` (
`id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`charge_transaction_id` VARCHAR(191) NOT NULL,
`license_id` VARCHAR(191) NULL,
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateIndex
CREATE UNIQUE INDEX `consumers_mobile_number_partner_id_key` ON `consumers`(`mobile_number`, `partner_id`);
-- CreateIndex
CREATE UNIQUE INDEX `consumers_national_code_partner_id_key` ON `consumers`(`national_code`, `partner_id`);
-- AddForeignKey
ALTER TABLE `licenses` ADD CONSTRAINT `licenses_charge_transaction_id_fkey` FOREIGN KEY (`charge_transaction_id`) REFERENCES `license_charged_transactions`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `licenses_activated` ADD CONSTRAINT `licenses_activated_license_id_fkey` FOREIGN KEY (`license_id`) REFERENCES `licenses`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `licenses_activated` ADD CONSTRAINT `licenses_activated_business_activity_id_fkey` FOREIGN KEY (`business_activity_id`) REFERENCES `business_activities`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_charged_transactions` ADD CONSTRAINT `license_charged_transactions_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_renew` ADD CONSTRAINT `license_renew_charge_transaction_id_fkey` FOREIGN KEY (`charge_transaction_id`) REFERENCES `license_renew_charge_transaction`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_renew` ADD CONSTRAINT `license_renew_activation_id_fkey` FOREIGN KEY (`activation_id`) REFERENCES `licenses_activated`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_renew_charge_transaction` ADD CONSTRAINT `license_renew_charge_transaction_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `partner_account_quota_charge_transaction` ADD CONSTRAINT `partner_account_quota_charge_transaction_partner_id_fkey` FOREIGN KEY (`partner_id`) REFERENCES `partners`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `partner_account_quota_allocation` ADD CONSTRAINT `partner_account_quota_allocation_charge_transaction_id_fkey` FOREIGN KEY (`charge_transaction_id`) REFERENCES `partner_account_quota_charge_transaction`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `partner_account_quota_allocation` ADD CONSTRAINT `partner_account_quota_allocation_license_id_fkey` FOREIGN KEY (`license_id`) REFERENCES `licenses`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
@@ -1,18 +0,0 @@
/*
Warnings:
- A unique constraint covering the columns `[tracking_code]` on the table `partner_account_quota_charge_transaction` will be added. If there are existing duplicate values, this will fail.
- Added the required column `expires_at` to the `license_renew` table without a default value. This is not possible if the table is not empty.
- Added the required column `activation_expires_at` to the `partner_account_quota_charge_transaction` table without a default value. This is not possible if the table is not empty.
- Added the required column `tracking_code` to the `partner_account_quota_charge_transaction` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE `license_renew` ADD COLUMN `expires_at` DATETIME(3) NOT NULL;
-- AlterTable
ALTER TABLE `partner_account_quota_charge_transaction` ADD COLUMN `activation_expires_at` DATETIME(3) NOT NULL,
ADD COLUMN `tracking_code` VARCHAR(191) NOT NULL;
-- CreateIndex
CREATE UNIQUE INDEX `partner_account_quota_charge_transaction_tracking_code_key` ON `partner_account_quota_charge_transaction`(`tracking_code`);
@@ -1,16 +0,0 @@
/*
Warnings:
- You are about to drop the column `serial` on the `poses` table. All the data in the column will be lost.
- A unique constraint covering the columns `[serial_number]` on the table `poses` will be added. If there are existing duplicate values, this will fail.
*/
-- DropIndex
DROP INDEX `poses_serial_key` ON `poses`;
-- AlterTable
ALTER TABLE `poses` DROP COLUMN `serial`,
ADD COLUMN `serial_number` VARCHAR(191) NULL;
-- CreateIndex
CREATE UNIQUE INDEX `poses_serial_number_key` ON `poses`(`serial_number`);
@@ -1,20 +0,0 @@
/*
Warnings:
- A unique constraint covering the columns `[account_id]` on the table `poses` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE `licenses` MODIFY `accounts_limit` INTEGER NOT NULL DEFAULT 2;
-- AlterTable
ALTER TABLE `partners` ADD COLUMN `logo_url` VARCHAR(191) NULL;
-- AlterTable
ALTER TABLE `poses` ADD COLUMN `account_id` VARCHAR(191) NULL;
-- CreateIndex
CREATE UNIQUE INDEX `poses_account_id_key` ON `poses`(`account_id`);
-- AddForeignKey
ALTER TABLE `poses` ADD CONSTRAINT `poses_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
@@ -1,50 +0,0 @@
/*
Warnings:
- You are about to drop the `partner_account_quota_allocation` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE `partner_account_quota_allocation` DROP FOREIGN KEY `partner_account_quota_allocation_charge_transaction_id_fkey`;
-- DropForeignKey
ALTER TABLE `partner_account_quota_allocation` DROP FOREIGN KEY `partner_account_quota_allocation_license_id_fkey`;
-- DropTable
DROP TABLE `partner_account_quota_allocation`;
-- CreateTable
CREATE TABLE `partner_account_quota_credit` (
`id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`charge_transaction_id` VARCHAR(191) NOT NULL,
`allocation_id` VARCHAR(191) NULL,
UNIQUE INDEX `partner_account_quota_credit_allocation_id_key`(`allocation_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `license_account_allocation` (
`id` VARCHAR(191) NOT NULL,
`created_at` TIMESTAMP(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0),
`updated_at` TIMESTAMP(0) NOT NULL,
`license_activation_id` VARCHAR(191) NOT NULL,
`account_id` VARCHAR(191) NOT NULL,
UNIQUE INDEX `license_account_allocation_account_id_key`(`account_id`),
PRIMARY KEY (`id`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- AddForeignKey
ALTER TABLE `partner_account_quota_credit` ADD CONSTRAINT `partner_account_quota_credit_charge_transaction_id_fkey` FOREIGN KEY (`charge_transaction_id`) REFERENCES `partner_account_quota_charge_transaction`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `partner_account_quota_credit` ADD CONSTRAINT `partner_account_quota_credit_allocation_id_fkey` FOREIGN KEY (`allocation_id`) REFERENCES `license_account_allocation`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_account_allocation` ADD CONSTRAINT `license_account_allocation_license_activation_id_fkey` FOREIGN KEY (`license_activation_id`) REFERENCES `licenses_activated`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_account_allocation` ADD CONSTRAINT `license_account_allocation_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -1,30 +0,0 @@
-- DropForeignKey
ALTER TABLE `license_account_allocation` DROP FOREIGN KEY `license_account_allocation_account_id_fkey`;
-- DropForeignKey
ALTER TABLE `license_account_allocation` DROP FOREIGN KEY `license_account_allocation_license_activation_id_fkey`;
-- DropForeignKey
ALTER TABLE `partner_account_quota_credit` DROP FOREIGN KEY `partner_account_quota_credit_charge_transaction_id_fkey`;
-- DropIndex
DROP INDEX `license_account_allocation_license_activation_id_fkey` ON `license_account_allocation`;
-- DropIndex
DROP INDEX `partner_account_quota_credit_charge_transaction_id_fkey` ON `partner_account_quota_credit`;
-- AlterTable
ALTER TABLE `license_account_allocation` MODIFY `license_activation_id` VARCHAR(191) NULL,
MODIFY `account_id` VARCHAR(191) NULL;
-- AlterTable
ALTER TABLE `partner_account_quota_credit` MODIFY `charge_transaction_id` VARCHAR(191) NULL;
-- AddForeignKey
ALTER TABLE `partner_account_quota_credit` ADD CONSTRAINT `partner_account_quota_credit_charge_transaction_id_fkey` FOREIGN KEY (`charge_transaction_id`) REFERENCES `partner_account_quota_charge_transaction`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_account_allocation` ADD CONSTRAINT `license_account_allocation_license_activation_id_fkey` FOREIGN KEY (`license_activation_id`) REFERENCES `licenses_activated`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_account_allocation` ADD CONSTRAINT `license_account_allocation_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
@@ -1,53 +0,0 @@
/*
Warnings:
- You are about to drop the column `allocation_id` on the `partner_account_quota_credit` table. All the data in the column will be lost.
- A unique constraint covering the columns `[economic_code,consumer_id]` on the table `business_activities` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[credit_id]` on the table `license_account_allocation` will be added. If there are existing duplicate values, this will fail.
- Made the column `account_id` on table `poses` required. This step will fail if there are existing NULL values in that column.
*/
-- DropForeignKey
ALTER TABLE `license_renew` DROP FOREIGN KEY `license_renew_activation_id_fkey`;
-- DropForeignKey
ALTER TABLE `partner_account_quota_credit` DROP FOREIGN KEY `partner_account_quota_credit_allocation_id_fkey`;
-- DropForeignKey
ALTER TABLE `poses` DROP FOREIGN KEY `poses_account_id_fkey`;
-- DropIndex
DROP INDEX `business_activities_economic_code_key` ON `business_activities`;
-- DropIndex
DROP INDEX `license_renew_activation_id_fkey` ON `license_renew`;
-- DropIndex
DROP INDEX `partner_account_quota_credit_allocation_id_key` ON `partner_account_quota_credit`;
-- AlterTable
ALTER TABLE `license_account_allocation` ADD COLUMN `credit_id` VARCHAR(191) NULL;
-- AlterTable
ALTER TABLE `license_renew` MODIFY `activation_id` VARCHAR(191) NULL;
-- AlterTable
ALTER TABLE `partner_account_quota_credit` DROP COLUMN `allocation_id`;
-- AlterTable
ALTER TABLE `poses` MODIFY `account_id` VARCHAR(191) NOT NULL;
-- CreateIndex
CREATE UNIQUE INDEX `business_activities_economic_code_consumer_id_key` ON `business_activities`(`economic_code`, `consumer_id`);
-- CreateIndex
CREATE UNIQUE INDEX `license_account_allocation_credit_id_key` ON `license_account_allocation`(`credit_id`);
-- AddForeignKey
ALTER TABLE `poses` ADD CONSTRAINT `poses_account_id_fkey` FOREIGN KEY (`account_id`) REFERENCES `consumer_accounts`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_renew` ADD CONSTRAINT `license_renew_activation_id_fkey` FOREIGN KEY (`activation_id`) REFERENCES `licenses_activated`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE `license_account_allocation` ADD CONSTRAINT `license_account_allocation_credit_id_fkey` FOREIGN KEY (`credit_id`) REFERENCES `partner_account_quota_credit`(`id`) ON DELETE SET NULL ON UPDATE CASCADE;
@@ -0,0 +1,25 @@
/*
Warnings:
- A unique constraint covering the columns `[business_activity_id,postal_code,national_id]` on the table `customer_individuals` will be added. If there are existing duplicate values, this will fail.
- A unique constraint covering the columns `[business_activity_id,postal_code,registration_number]` on the table `customer_legal` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE `customer_individuals` MODIFY `first_name` VARCHAR(255) NULL,
MODIFY `last_name` VARCHAR(255) NULL,
MODIFY `national_id` CHAR(10) NULL,
MODIFY `mobile_number` CHAR(15) NULL,
MODIFY `postal_code` CHAR(10) NULL,
MODIFY `economic_code` CHAR(14) NULL;
-- AlterTable
ALTER TABLE `customer_legal` MODIFY `name` VARCHAR(255) NULL,
MODIFY `economic_code` CHAR(11) NULL,
MODIFY `postal_code` CHAR(10) NULL;
-- CreateIndex
CREATE UNIQUE INDEX `customer_individuals_business_activity_id_postal_code_nation_key` ON `customer_individuals`(`business_activity_id`, `postal_code`, `national_id`);
-- CreateIndex
CREATE UNIQUE INDEX `customer_legal_business_activity_id_postal_code_registration_key` ON `customer_legal`(`business_activity_id`, `postal_code`, `registration_number`);
@@ -0,0 +1,50 @@
-- ALTER TABLE
ALTER TABLE `sales_invoices`
ADD COLUMN `last_attempt_no` INT NULL,
ADD COLUMN `last_tsp_status` ENUM(
'NOT_SEND',
'QUEUED',
'FISCAL_QUEUED',
'SEND_FAILURE',
'SUCCESS',
'FAILURE'
) NULL;
-- INDEX
CREATE INDEX `sales_invoices_pos_id_invoice_date_idx`
ON `sales_invoices` (`pos_id`, `invoice_date`);
-- BACKFILL LAST ATTEMPT
UPDATE sales_invoices si
JOIN (
SELECT invoice_id, attempt_no, status
FROM (
SELECT
invoice_id,
attempt_no,
status,
ROW_NUMBER() OVER (
PARTITION BY invoice_id
ORDER BY created_at DESC
) rn
FROM sale_invoice_tsp_attempts
) t
WHERE rn = 1
) la ON la.invoice_id = si.id
SET
si.last_attempt_no = la.attempt_no,
si.last_tsp_status = la.status;
-- SET NOT_SEND FOR NO ATTEMPTS
UPDATE sales_invoices si
LEFT JOIN sale_invoice_tsp_attempts att
ON att.invoice_id = si.id
SET
si.last_attempt_no = NULL,
si.last_tsp_status = 'NOT_SEND'
WHERE att.invoice_id IS NULL;
-- SAFETY FILL
UPDATE sales_invoices
SET last_tsp_status = 'NOT_SEND'
WHERE last_tsp_status IS NULL;
+8 -36
View File
@@ -1,53 +1,25 @@
model AdminAccount { model AdminAccount {
id String @id @default(ulid()) id String @id @default(ulid())
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
admin_id String admin_id String
account_id String @unique
account Account @relation(fields: [account_id], references: [id], onDelete: Cascade)
admin Admin @relation(fields: [admin_id], references: [id]) admin Admin @relation(fields: [admin_id], references: [id])
account_id String @unique @@index([admin_id], map: "admin_accounts_admin_id_fkey")
account Account @relation(fields: [account_id], references: [id])
@@map("admin_accounts") @@map("admin_accounts")
} }
model Admin { model Admin {
id String @id @default(ulid()) id String @id @default(ulid())
mobile_number String @unique() mobile_number String @unique
national_code String? @unique() national_code String? @unique
first_name String first_name String
last_name String last_name String
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
accounts AdminAccount[]
adminAccounts AdminAccount[]
@@map("admins") @@map("admins")
} }
// model LegalProfile {
// user_id String @id
// company_name String
// register_no String @unique()
// representative_name String?
// representative_national_id String?
// // user User @relation(fields: [user_id], references: [id])
// @@map("legal_profiles")
// }
// model IndividualProfile {
// user_id String @id
// national_code String @unique()
// first_name String
// last_name String
// birth_date DateTime?
// mobile_number String
// // user User @relation(fields: [user_id], references: [id])
// @@map("individual_profiles")
// }
+3 -34
View File
@@ -1,44 +1,13 @@
model Account { model Account {
id String @id @default(ulid()) id String @id @default(ulid())
username String @unique() username String @unique
password String password String
status AccountStatus status AccountStatus
type AccountType type AccountType
admin_account AdminAccount? admin_account AdminAccount?
provider_account ProviderAccount?
partner_account PartnerAccount?
consumer_account ConsumerAccount? consumer_account ConsumerAccount?
partner_account PartnerAccount?
// tokens Token[] provider_account ProviderAccount?
// refresh_tokens RefreshToken[]
@@map("accounts") @@map("accounts")
} }
// model Token {
// id String @id @d @uniqueefault(ulid())
// token String @unique
// type TokenType
// created_at DateTime @default(now())
// expires_at DateTime
// account_id String
// account Account @relation(fields: [account_id], references: [id])
// @@unique([type, account_id])
// @@map("tokens")
// }
// model VerificationCode {
// id String @id @default(ulid())
// code String
// is_used Boolean @default(false)
// created_at DateTime @default(now())
// expires_at DateTime
// account_id String
// account Account @relation(fields: [account_id], references: [id])
// @@map("verification_codes")
// }
-117
View File
@@ -1,117 +0,0 @@
model ConsumerAccount {
id String @id @default(ulid())
role ConsumerRole
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0)
consumer_id String
consumer Consumer @relation(fields: [consumer_id], references: [id])
account_id String @unique()
account Account @relation(fields: [account_id], references: [id])
pos Pos?
permission PermissionConsumer?
account_allocation LicenseAccountAllocation?
sales_invoices SalesInvoice[]
@@map("consumer_accounts")
}
model Consumer {
id String @id @default(ulid())
mobile_number String
national_code String
first_name String
last_name String
status ConsumerStatus @default(ACTIVE)
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0)
partner_id String
partner Partner @relation(fields: [partner_id], references: [id])
consumer_accounts ConsumerAccount[]
business_activities BusinessActivity[]
consumer_devices ConsumerDevices[]
@@unique([mobile_number, partner_id])
@@unique([national_code, partner_id])
@@map("consumers")
}
model BusinessActivity {
id String @id @default(ulid())
economic_code String
name String
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0)
guild_id String
guild Guild @relation(fields: [guild_id], references: [id])
consumer_id String
consumer Consumer @relation(fields: [consumer_id], references: [id])
complexes Complex[]
permission_businesses PermissionBusiness[]
license_activation LicenseActivation?
@@unique([economic_code, consumer_id])
@@map("business_activities")
}
model Complex {
id String @id @default(ulid())
name String
branch_code String
address String?
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0)
business_activity_id String
business_activity BusinessActivity @relation(fields: [business_activity_id], references: [id])
pos_list Pos[]
goods Good[]
good_categories GoodCategory[]
permission_complexes PermissionComplex[]
customer_individuals CustomerIndividual[]
customer_legals CustomerLegal[]
@@map("complexes")
}
model Pos {
id String @id @default(ulid())
name String
model String?
serial_number String? @unique()
status POSStatus @default(ACTIVE)
pos_type POSType
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0)
complex_id String
complex Complex @relation(fields: [complex_id], references: [id])
device_id String?
device Device? @relation(fields: [device_id], references: [id])
provider_id String?
provider Provider? @relation(fields: [provider_id], references: [id])
account_id String @unique
account ConsumerAccount @relation(fields: [account_id], references: [id])
permission_pos PermissionPos[]
sales_invoices SalesInvoice[]
@@map("poses")
}
+3 -7
View File
@@ -1,10 +1,8 @@
model DeviceBrand { model DeviceBrand {
id String @id @default(ulid()) id String @id @default(ulid())
name String name String
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
devices Device[] devices Device[]
@@map("device_brands") @@map("device_brands")
@@ -14,14 +12,12 @@ model Device {
id String @id @default(ulid()) id String @id @default(ulid())
name String name String
os_version String? os_version String?
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
brand_id String brand_id String
brand DeviceBrand @relation(fields: [brand_id], references: [id]) brand DeviceBrand @relation(fields: [brand_id], references: [id])
poses Pos[] poses Pos[]
@@index([brand_id])
@@map("devices") @@map("devices")
} }
+30 -48
View File
@@ -1,51 +1,43 @@
model LicenseChargeTransaction { model LicenseChargeTransaction {
id String @id @default(ulid()) id String @id @default(ulid())
activation_expires_at DateTime activation_expires_at DateTime
tracking_code String @unique() tracking_code String @unique
purchased_count Int purchased_count Int
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
partner_id String partner_id String
partner Partner @relation(fields: [partner_id], references: [id]) partner Partner @relation(fields: [partner_id], references: [id])
licenses License[] licenses License[]
@@index([partner_id])
@@map("license_charged_transactions") @@map("license_charged_transactions")
} }
model License { model License {
id String @id @default(ulid()) id String @id @default(ulid())
accounts_limit Int @default(2) accounts_limit Int @default(2)
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
charge_transaction_id String charge_transaction_id String
charge_transaction LicenseChargeTransaction @relation(fields: [charge_transaction_id], references: [id]) charge_transaction LicenseChargeTransaction @relation(fields: [charge_transaction_id], references: [id])
activation LicenseActivation? activation LicenseActivation?
@@index([charge_transaction_id])
@@map("licenses") @@map("licenses")
} }
model LicenseActivation { model LicenseActivation {
id String @id @unique() @default(ulid()) id String @id @unique @default(ulid())
starts_at DateTime starts_at DateTime
expires_at DateTime expires_at DateTime
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
license_id String @unique license_id String @unique
license License @relation(fields: [license_id], references: [id])
business_activity_id String @unique business_activity_id String @unique
business_activity BusinessActivity @relation(fields: [business_activity_id], references: [id])
license_renews LicenseRenew[]
account_allocations LicenseAccountAllocation[] account_allocations LicenseAccountAllocation[]
license_renews LicenseRenew[]
business_activity BusinessActivity @relation(fields: [business_activity_id], references: [id])
license License @relation(fields: [license_id], references: [id])
@@map("licenses_activated") @@map("licenses_activated")
} }
@@ -53,81 +45,71 @@ model LicenseActivation {
model LicenseRenewChargeTransaction { model LicenseRenewChargeTransaction {
id String @id @default(ulid()) id String @id @default(ulid())
activation_expires_at DateTime activation_expires_at DateTime
tracking_code String @unique() tracking_code String @unique
purchased_count Int purchased_count Int
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
partner_id String partner_id String
license_renews LicenseRenew[]
partner Partner @relation(fields: [partner_id], references: [id]) partner Partner @relation(fields: [partner_id], references: [id])
license_renews LicenseRenew[] @@index([partner_id])
@@map("license_renew_charge_transaction") @@map("license_renew_charge_transaction")
} }
model LicenseRenew { model LicenseRenew {
id String @id @default(ulid()) id String @id @default(ulid())
expires_at DateTime expires_at DateTime
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
charge_transaction_id String charge_transaction_id String
charge_transaction LicenseRenewChargeTransaction @relation(fields: [charge_transaction_id], references: [id])
activation_id String? activation_id String?
activation LicenseActivation? @relation(fields: [activation_id], references: [id]) activation LicenseActivation? @relation(fields: [activation_id], references: [id])
charge_transaction LicenseRenewChargeTransaction @relation(fields: [charge_transaction_id], references: [id])
@@index([activation_id])
@@index([charge_transaction_id])
@@map("license_renew") @@map("license_renew")
} }
model PartnerAccountQuotaChargeTransaction { model PartnerAccountQuotaChargeTransaction {
id String @id @default(ulid()) id String @id @default(ulid())
activation_expires_at DateTime activation_expires_at DateTime
tracking_code String @unique() tracking_code String @unique
purchased_count Int purchased_count Int
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
partner_id String partner_id String
partner Partner @relation(fields: [partner_id], references: [id]) partner Partner @relation(fields: [partner_id], references: [id])
credits PartnerAccountQuotaCredit[] credits PartnerAccountQuotaCredit[]
@@index([partner_id])
@@map("partner_account_quota_charge_transaction") @@map("partner_account_quota_charge_transaction")
} }
model PartnerAccountQuotaCredit { model PartnerAccountQuotaCredit {
id String @id @default(ulid()) id String @id @default(ulid())
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
charge_transaction_id String? charge_transaction_id String?
allocation LicenseAccountAllocation?
charge_transaction PartnerAccountQuotaChargeTransaction? @relation(fields: [charge_transaction_id], references: [id]) charge_transaction PartnerAccountQuotaChargeTransaction? @relation(fields: [charge_transaction_id], references: [id])
allocation LicenseAccountAllocation? @@index([charge_transaction_id])
@@map("partner_account_quota_credit") @@map("partner_account_quota_credit")
} }
model LicenseAccountAllocation { model LicenseAccountAllocation {
id String @id @default(ulid()) id String @id @default(ulid())
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
license_activation_id String? license_activation_id String?
account_id String? @unique
credit_id String? @unique
account ConsumerAccount? @relation(fields: [account_id], references: [id])
credit PartnerAccountQuotaCredit? @relation(fields: [credit_id], references: [id])
license_activation LicenseActivation? @relation(fields: [license_activation_id], references: [id]) license_activation LicenseActivation? @relation(fields: [license_activation_id], references: [id])
account_id String? @unique @@index([license_activation_id])
account ConsumerAccount? @relation(fields: [account_id], references: [id])
credit_id String? @unique
credit PartnerAccountQuotaCredit? @relation(fields: [credit_id], references: [id])
@@map("license_account_allocation") @@map("license_account_allocation")
} }
+9 -11
View File
@@ -1,34 +1,32 @@
model PartnerAccount { model PartnerAccount {
id String @id @default(ulid()) id String @id @default(ulid())
role PartnerRole role PartnerRole
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0) updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0)
partner_id String partner_id String
account_id String @unique
account Account @relation(fields: [account_id], references: [id], onDelete: Cascade)
partner Partner @relation(fields: [partner_id], references: [id]) partner Partner @relation(fields: [partner_id], references: [id])
account_id String @unique @@index([partner_id], map: "partner_accounts_partner_id_fkey")
account Account @relation(fields: [account_id], references: [id])
@@map("partner_accounts") @@map("partner_accounts")
} }
model Partner { model Partner {
id String @id @default(ulid()) id String @id @default(ulid())
name String name String
code String @unique() code String @unique
status PartnerStatus @default(ACTIVE) status PartnerStatus @default(ACTIVE)
logo_url String? logo_url String?
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0) updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0)
tsp_provider TspProviderType @default(NAMA)
consumers Consumer[] consumers_individual ConsumerIndividual[]
partner_accounts PartnerAccount[] consumers_legal ConsumerLegal[]
license_charge_transactions LicenseChargeTransaction[] license_charge_transactions LicenseChargeTransaction[]
account_quota_charge_transactions PartnerAccountQuotaChargeTransaction[]
license_renew_charge_transactions LicenseRenewChargeTransaction[] license_renew_charge_transactions LicenseRenewChargeTransaction[]
account_quota_charge_transactions PartnerAccountQuotaChargeTransaction[]
accounts PartnerAccount[]
@@map("partners") @@map("partners")
} }
+7 -12
View File
@@ -1,12 +1,10 @@
model PermissionConsumer { model PermissionConsumer {
id String @id @default(ulid()) id String @id @default(ulid())
consumer_account_id String @unique consumer_account_id String @unique
consumer_account ConsumerAccount @relation(fields: [consumer_account_id], references: [id], onDelete: Cascade)
pos_permissions PermissionPos[]
complex_permissions PermissionComplex[]
business_permissions PermissionBusiness[] business_permissions PermissionBusiness[]
complex_permissions PermissionComplex[]
consumer_account ConsumerAccount @relation(fields: [consumer_account_id], references: [id], onDelete: Cascade)
pos_permissions PermissionPos[]
@@map("permission_consumers") @@map("permission_consumers")
} }
@@ -14,41 +12,38 @@ model PermissionConsumer {
model PermissionPos { model PermissionPos {
id String @id @default(ulid()) id String @id @default(ulid())
role POSRole role POSRole
pos_id String pos_id String
permission_id String permission_id String
pos Pos @relation(fields: [pos_id], references: [id], onDelete: Cascade)
permission PermissionConsumer @relation(fields: [permission_id], references: [id], onDelete: Cascade) permission PermissionConsumer @relation(fields: [permission_id], references: [id], onDelete: Cascade)
pos Pos @relation(fields: [pos_id], references: [id], onDelete: Cascade)
@@unique([permission_id, pos_id]) @@unique([permission_id, pos_id])
@@index([pos_id], map: "permission_poses_pos_id_fkey")
@@map("permission_poses") @@map("permission_poses")
} }
model PermissionComplex { model PermissionComplex {
id String @id @default(ulid()) id String @id @default(ulid())
role ComplexRole role ComplexRole
complex_id String complex_id String
permission_id String permission_id String
complex Complex @relation(fields: [complex_id], references: [id], onDelete: Cascade) complex Complex @relation(fields: [complex_id], references: [id], onDelete: Cascade)
permission PermissionConsumer @relation(fields: [permission_id], references: [id], onDelete: Cascade) permission PermissionConsumer @relation(fields: [permission_id], references: [id], onDelete: Cascade)
@@unique([permission_id, complex_id]) @@unique([permission_id, complex_id])
@@index([complex_id], map: "permission_complexes_complex_id_fkey")
@@map("permission_complexes") @@map("permission_complexes")
} }
model PermissionBusiness { model PermissionBusiness {
id String @id @default(ulid()) id String @id @default(ulid())
role BusinessRole role BusinessRole
business_id String business_id String
permission_id String permission_id String
business BusinessActivity @relation(fields: [business_id], references: [id], onDelete: Cascade) business BusinessActivity @relation(fields: [business_id], references: [id], onDelete: Cascade)
permission PermissionConsumer @relation(fields: [permission_id], references: [id], onDelete: Cascade) permission PermissionConsumer @relation(fields: [permission_id], references: [id], onDelete: Cascade)
@@unique([permission_id, business_id]) @@unique([permission_id, business_id])
@@index([business_id], map: "permission_business_activities_business_id_fkey")
@@map("permission_business_activities") @@map("permission_business_activities")
} }
+6 -10
View File
@@ -1,30 +1,26 @@
model ProviderAccount { model ProviderAccount {
id String @id @default(ulid()) id String @id @default(ulid())
role ProviderRole role ProviderRole
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
provider_id String provider_id String
account_id String @unique
account Account @relation(fields: [account_id], references: [id], onDelete: Cascade)
provider Provider @relation(fields: [provider_id], references: [id]) provider Provider @relation(fields: [provider_id], references: [id])
account_id String @unique @@index([provider_id], map: "provider_accounts_provider_id_fkey")
account Account @relation(fields: [account_id], references: [id])
@@map("provider_accounts") @@map("provider_accounts")
} }
model Provider { model Provider {
id String @id @default(ulid()) id String @id @default(ulid())
code String @unique() code String @unique
status PartnerStatus @default(ACTIVE) status PartnerStatus @default(ACTIVE)
name String name String
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
pos_list Pos[] pos_list Pos[]
provider_accounts ProviderAccount[] accounts ProviderAccount[]
@@map("providers") @@map("providers")
} }
@@ -0,0 +1,12 @@
model ConsumerAccountDevice {
id String @id @default(ulid())
device_id String @unique
device_name String?
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
consumer_account_id String @unique
consumer_account ConsumerAccount @relation(fields: [consumer_account_id], references: [id])
@@index([consumer_account_id])
@@map("consumer_account_device")
}
@@ -1,24 +1 @@
model ConsumerDevices {
uuid String @id @unique() @db.VarChar(255)
app_version String @db.VarChar(20)
build_number String @db.VarChar(20)
platform String @db.VarChar(100)
brand String @db.VarChar(100)
model String @db.VarChar(100)
device String @db.VarChar(100)
publisher ApplicationPublisher
os_version String @db.VarChar(20)
sdk_version String @db.VarChar(20)
release_number String @db.VarChar(20)
user_agent String? @db.VarChar(100)
browser_name String? @db.VarChar(100)
fcm_token String? @db.VarChar(100)
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0)
consumer_id String
consumer Consumer @relation(fields: [consumer_id], references: [id])
@@map("consumer_devices")
}
@@ -6,11 +6,9 @@ model ApplicationReleasedInfo {
release_type ApplicationReleaseType release_type ApplicationReleaseType
platform ApplicationPlatform platform ApplicationPlatform
notes Json? notes Json?
release_date DateTime @default(now()) @db.Timestamp(0) release_date DateTime @default(now()) @db.Timestamp(0)
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
@@map("application_released_info") @@map("application_released_info")
} }
+146
View File
@@ -0,0 +1,146 @@
model ConsumerAccount {
id String @id @default(ulid())
role ConsumerRole
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
consumer_id String
account_id String @unique
account_device ConsumerAccountDevice?
consumer_account_good_favorites ConsumerAccountGoodFavorite[]
account Account @relation(fields: [account_id], references: [id])
consumer Consumer @relation(fields: [consumer_id], references: [id])
account_allocation LicenseAccountAllocation?
permission PermissionConsumer?
pos Pos?
sales_invoices SalesInvoice[]
@@index([consumer_id], map: "consumer_accounts_consumer_id_fkey")
@@map("consumer_accounts")
}
model Consumer {
id String @id @default(ulid())
type ConsumerType
status ConsumerStatus @default(ACTIVE)
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
business_activities BusinessActivity[]
accounts ConsumerAccount[]
individual ConsumerIndividual?
legal ConsumerLegal?
@@map("consumers")
}
model ConsumerIndividual {
first_name String
last_name String
mobile_number String
national_code String
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
partner_id String
consumer_id String @id
consumer Consumer @relation(fields: [consumer_id], references: [id], onDelete: Cascade)
partner Partner @relation(fields: [partner_id], references: [id])
@@unique([mobile_number, consumer_id])
@@unique([partner_id, national_code])
@@map("consumers_individual")
}
model ConsumerLegal {
name String
registration_code String
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
partner_id String
consumer_id String @id
consumer Consumer @relation(fields: [consumer_id], references: [id], onDelete: Cascade)
partner Partner @relation(fields: [partner_id], references: [id])
@@unique([partner_id, registration_code])
@@map("consumers_legal")
}
model BusinessActivity {
id String @id @default(ulid())
economic_code String
name String
fiscal_id String
partner_token String
invoice_number_sequence Decimal @default(1) @db.Decimal(20, 0)
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
guild_id String
consumer_id String
consumer Consumer @relation(fields: [consumer_id], references: [id])
guild Guild @relation(fields: [guild_id], references: [id])
complexes Complex[]
customer_individuals CustomerIndividual[]
customer_legals CustomerLegal[]
goods Good[]
license_activation LicenseActivation?
permission_businesses PermissionBusiness[]
@@unique([economic_code, consumer_id])
@@index([consumer_id], map: "business_activities_consumer_id_fkey")
@@index([guild_id], map: "business_activities_guild_id_fkey")
@@map("business_activities")
}
model Complex {
id String @id @default(ulid())
name String
branch_code String
address String?
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
business_activity_id String
business_activity BusinessActivity @relation(fields: [business_activity_id], references: [id])
good_categories GoodCategory[]
permission_complexes PermissionComplex[]
pos_list Pos[]
@@index([business_activity_id], map: "complexes_business_activity_id_fkey")
@@map("complexes")
}
model Pos {
id String @id @default(ulid())
name String
model String?
serial_number String? @unique
status POSStatus @default(ACTIVE)
pos_type POSType
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
complex_id String
device_id String?
provider_id String?
account_id String @unique
permission_pos PermissionPos[]
account ConsumerAccount @relation(fields: [account_id], references: [id])
complex Complex @relation(fields: [complex_id], references: [id])
device Device? @relation(fields: [device_id], references: [id])
provider Provider? @relation(fields: [provider_id], references: [id])
sales_invoices SalesInvoice[]
@@index([complex_id], map: "poses_complex_id_fkey")
@@index([device_id], map: "poses_device_id_fkey")
@@index([provider_id], map: "poses_provider_id_fkey")
@@map("poses")
}
model ConsumerAccountGoodFavorite {
created_at DateTime @default(now())
consumer_account_id String
good_id String
consumer_account ConsumerAccount @relation(fields: [consumer_account_id], references: [id], onDelete: Cascade)
good Good @relation(fields: [good_id], references: [id], onDelete: Cascade)
@@id([consumer_account_id, good_id])
@@index([good_id])
@@index([consumer_account_id])
@@map("consumer_account_good_favorites")
}
+50
View File
@@ -0,0 +1,50 @@
model Good {
id String @id @default(ulid())
name String @db.VarChar(255)
is_default_guild_good Boolean @default(false)
pricing_model GoodPricingModel
description String? @db.Text
local_sku String? @unique @db.VarChar(100)
barcode String? @unique @db.VarChar(100)
base_sale_price Decimal? @default(0) @db.Decimal(15, 0)
image_url String? @db.VarChar(255)
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
deleted_at DateTime? @db.Timestamp(0)
sku_id String
measure_unit_id String
category_id String?
business_activity_id String?
consumer_account_good_favorites ConsumerAccountGoodFavorite[]
business_activity BusinessActivity? @relation(fields: [business_activity_id], references: [id])
category GoodCategory? @relation(fields: [category_id], references: [id])
measure_unit MeasureUnits @relation(fields: [measure_unit_id], references: [id])
sku StockKeepingUnits @relation(fields: [sku_id], references: [id])
sales_invoice_items SalesInvoiceItem[]
@@index([category_id])
@@index([business_activity_id], map: "goods_business_activity_id_fkey")
@@index([measure_unit_id], map: "goods_measure_unit_id_fkey")
@@index([sku_id], map: "goods_sku_id_fkey")
@@map("goods")
}
model GoodCategory {
id String @id @default(ulid())
name String @db.VarChar(100)
description String? @db.Text
image_url String? @db.VarChar(255)
complex_id String?
is_default_guild_good Boolean @default(false)
guild_id String?
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
deleted_at DateTime? @db.Timestamp(0)
complex Complex? @relation(fields: [complex_id], references: [id])
guild Guild? @relation(fields: [guild_id], references: [id])
goods Good[]
@@index([complex_id], map: "good_categories_complex_id_fkey")
@@index([guild_id], map: "good_categories_guild_id_fkey")
@@map("good_categories")
}
+13
View File
@@ -0,0 +1,13 @@
model Guild {
id String @id @default(ulid())
name String
invoice_template InvoiceTemplateType
code String?
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
business_activities BusinessActivity[]
good_categories GoodCategory[]
stockKeepingUnits StockKeepingUnits[]
@@map("guilds")
}
@@ -0,0 +1,10 @@
model MeasureUnits {
id String @id @default(ulid())
code String @unique
name String
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @default(now()) @updatedAt @db.Timestamp(0)
goods Good[]
@@map("measure_units")
}
+17
View File
@@ -0,0 +1,17 @@
model StockKeepingUnits {
id String @id @default(ulid())
code String @unique
name String
VAT Decimal @db.Decimal(5, 2)
is_public Boolean @default(true)
is_domestic Boolean @default(false)
guild_id String
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
goods Good[]
guild Guild @relation(fields: [guild_id], references: [id])
@@index([code])
@@index([guild_id], map: "stock_keeping_units_guild_id_fkey")
@@map("stock_keeping_units")
}
+1 -12
View File
@@ -1,19 +1,8 @@
// generator client {
// provider = "prisma-client-js"
// // output = "../../src/generated/prisma"
// // moduleFormat = "cjs"
// previewFeatures = ["views"]
// }
// datasource db {
// provider = "mysql"
// }
generator client { generator client {
provider = "prisma-client" provider = "prisma-client"
output = "../../src/generated/prisma" output = "../../src/generated/prisma"
moduleFormat = "cjs"
previewFeatures = ["views"] previewFeatures = ["views"]
moduleFormat = "cjs"
} }
datasource db { datasource db {
+23 -27
View File
@@ -1,51 +1,47 @@
model Customer { model Customer {
id String @id @default(ulid()) id String @id @default(ulid())
is_favorite Boolean? @default(false) is_favorite Boolean? @default(false)
type CustomerType
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
deleted_at DateTime? @db.Timestamp(0) deleted_at DateTime? @db.Timestamp(0)
individual CustomerIndividual?
type CustomerType legal CustomerLegal?
unknown_customer Json?
customer_individual CustomerIndividual?
customer_legal CustomerLegal?
sales_invoices SalesInvoice[] sales_invoices SalesInvoice[]
@@map("customers") @@map("customers")
} }
model CustomerIndividual { model CustomerIndividual {
first_name String @db.VarChar(255) first_name String? @db.VarChar(255)
last_name String @db.VarChar(255) last_name String? @db.VarChar(255)
national_id String @db.Char(10) national_id String? @db.Char(10)
postal_code String @db.Char(10) mobile_number String? @db.Char(15)
economic_code String? @db.Char(10) postal_code String? @db.Char(10)
economic_code String? @db.Char(14)
customer_id String @id customer_id String @id
business_activity_id String
business_activity BusinessActivity @relation(fields: [business_activity_id], references: [id])
customer Customer @relation(fields: [customer_id], references: [id], onDelete: Cascade) customer Customer @relation(fields: [customer_id], references: [id], onDelete: Cascade)
complex_id String @@unique([business_activity_id, national_id])
complex Complex @relation(fields: [complex_id], references: [id]) @@unique([business_activity_id, postal_code, national_id])
@@index([business_activity_id])
@@unique([complex_id, national_id])
@@index([complex_id])
@@map("customer_individuals") @@map("customer_individuals")
} }
model CustomerLegal { model CustomerLegal {
company_name String @db.VarChar(255) name String? @db.VarChar(255)
economic_code String @db.Char(10) economic_code String? @db.Char(11)
registration_number String @db.Char(20) registration_number String? @db.Char(20)
postal_code String @db.Char(10) postal_code String? @db.Char(10)
customer_id String @id customer_id String @id
business_activity_id String
business_activity BusinessActivity @relation(fields: [business_activity_id], references: [id])
customer Customer @relation(fields: [customer_id], references: [id], onDelete: Cascade) customer Customer @relation(fields: [customer_id], references: [id], onDelete: Cascade)
complex_id String @@unique([business_activity_id, economic_code])
complex Complex @relation(fields: [complex_id], references: [id]) @@unique([business_activity_id, postal_code, registration_number])
@@index([business_activity_id])
@@unique([complex_id, registration_number])
@@index([complex_id])
@@map("customer_legal") @@map("customer_legal")
} }
+48 -55
View File
@@ -1,23 +1,14 @@
enum PaymentMethodType { enum PaymentMethodType {
TERMINAL CHEQUE
CASH
SET_OFF SET_OFF
CASH
TERMINAL
PAYMENT_GATEWAY
CARD CARD
BANK BANK
CHECK
OTHER OTHER
} }
enum UnitType {
COUNT
GRAM
KILOGRAM
MILLILITER
LITER
METER
HOUR
}
enum GoodPricingModel { enum GoodPricingModel {
STANDARD STANDARD
GOLD GOLD
@@ -49,18 +40,6 @@ enum BusinessRole {
OPERATOR OPERATOR
} }
enum LicenseType {
BASIC
PRO
ENTERPRISE
}
enum LicenseStatus {
ACTIVE
EXPIRED
SUSPENDED
}
enum POSType { enum POSType {
PSP PSP
MOBILE MOBILE
@@ -68,11 +47,6 @@ enum POSType {
API API
} }
enum UserType {
LEGAL
INDIVIDUAL
}
enum AccountType { enum AccountType {
ADMIN ADMIN
PROVIDER PROVIDER
@@ -80,17 +54,6 @@ enum AccountType {
CONSUMER CONSUMER
} }
enum UserStatus {
ACTIVE
INACTIVE
}
enum AccountRole {
OWNER
OPERATOR
ACCOUNTANT
}
enum AccountStatus { enum AccountStatus {
ACTIVE ACTIVE
SUSPENDED SUSPENDED
@@ -113,11 +76,6 @@ enum ProviderRole {
OPERATOR OPERATOR
} }
enum ProviderStatus {
ACTIVE
SUSPENDED
}
enum ConsumerRole { enum ConsumerRole {
OWNER OWNER
MANAGER MANAGER
@@ -129,11 +87,6 @@ enum ConsumerStatus {
SUSPENDED SUSPENDED
} }
enum TokenType {
ACCESS
REFRESH
}
enum ApplicationPlatform { enum ApplicationPlatform {
ANDROID ANDROID
IOS IOS
@@ -145,8 +98,48 @@ enum ApplicationReleaseType {
ALPHA ALPHA
} }
enum ApplicationPublisher { enum ConsumerType {
DIRECT INDIVIDUAL
CAFE_BAZAR LEGAL
MAYKET }
enum InvoiceSettlementType {
CASH
CREDIT
MIXED
}
enum TspProviderType {
NAMA
SUN
}
enum TspProviderResponseStatus {
NOT_SEND
QUEUED
FISCAL_QUEUED
SEND_FAILURE
SUCCESS
FAILURE
}
enum TspProviderRequestType {
ORIGINAL
CORRECTION
REVOKE
RETURN
}
enum InvoiceTemplateType {
SALE
FX_SALE
GOLD_JEWELRY
CONTRACT
UTILITY
AIR_TICKET
EXPORT
BILL_OF_LADING
PETROCHEMICAL
COMMODITY_EXCHANGE
INSURANCE
} }
-47
View File
@@ -1,47 +0,0 @@
model Good {
id String @id @default(ulid())
name String @db.VarChar(255)
is_default_guild_good Boolean @default(false)
sku String @db.VarChar(100)
unit_type UnitType
pricing_model GoodPricingModel
description String? @db.Text
local_sku String? @unique() @db.VarChar(100)
barcode String? @unique() @db.VarChar(100)
base_sale_price Decimal? @default(0.00) @db.Decimal(15, 0)
image_url String? @db.VarChar(255)
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
deleted_at DateTime? @db.Timestamp(0)
complex_id String?
category_id String?
category GoodCategory? @relation(fields: [category_id], references: [id])
complex Complex? @relation(fields: [complex_id], references: [id])
sales_invoice_items SalesInvoiceItem[]
@@index([category_id])
@@map("goods")
}
model GoodCategory {
id String @id @default(ulid())
name String @db.VarChar(100)
description String? @db.Text
image_url String? @db.VarChar(255)
complex_id String?
is_default_guild_good Boolean @default(false)
guild_id String?
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0)
deleted_at DateTime? @db.Timestamp(0)
goods Good[]
guild Guild? @relation(fields: [guild_id], references: [id])
complex Complex? @relation(fields: [complex_id], references: [id])
@@map("good_categories")
}
-13
View File
@@ -1,13 +0,0 @@
model Guild {
id String @id @default(ulid())
name String
code String?
created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt() @db.Timestamp(0)
business_activities BusinessActivity[]
good_categories GoodCategory[]
@@map("guilds")
}
+82 -20
View File
@@ -1,60 +1,122 @@
model SalesInvoice { model SalesInvoice {
id String @id @default(ulid()) id String @id @default(uuid())
code String @unique @db.VarChar(100) code String @unique @db.VarChar(100)
total_amount Decimal @db.Decimal(15, 2) total_amount Decimal @db.Decimal(15, 2)
invoice_number Int
invoice_date DateTime @default(now()) @db.Timestamp(0)
type TspProviderRequestType
tax_id String? @unique @db.VarChar(32)
notes String? @db.Text notes String? @db.Text
unknown_customer Json? @db.Json unknown_customer Json?
invoice_date DateTime? @default(now()) @db.Timestamp(0)
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
main_id String? @db.VarChar(50)
ref_id String? @unique @db.VarChar(50)
customer_id String? customer_id String?
account_id String consumer_account_id String
pos_id String pos_id String
settlement_type InvoiceSettlementType
customer Customer? @relation(fields: [customer_id], references: [id]) discount_amount Decimal? @db.Decimal(15, 2)
pos Pos @relation(fields: [pos_id], references: [id]) tax_amount Decimal? @db.Decimal(15, 2)
consumer_account ConsumerAccount @relation(fields: [account_id], references: [id]) last_tsp_status TspProviderResponseStatus?
last_attempt_no Int?
tsp_attempts SaleInvoiceTspAttempts[]
items SalesInvoiceItem[] items SalesInvoiceItem[]
payments SalesInvoicePayment[] payments SalesInvoicePayment[]
consumer_account ConsumerAccount @relation(fields: [consumer_account_id], references: [id])
customer Customer? @relation(fields: [customer_id], references: [id])
pos Pos @relation(fields: [pos_id], references: [id])
reference_invoice SalesInvoice? @relation("SalesInvoiceReference", fields: [ref_id], references: [id])
referenced_by SalesInvoice? @relation("SalesInvoiceReference")
@@unique([invoice_number, pos_id])
@@index([pos_id, invoice_date])
@@index([ref_id])
@@index([tax_id])
@@index([consumer_account_id], map: "sales_invoices_consumer_account_id_fkey")
@@index([customer_id], map: "sales_invoices_customer_id_fkey")
@@map("sales_invoices") @@map("sales_invoices")
} }
model SalesInvoiceItem { model SalesInvoiceItem {
id String @id @default(ulid()) id String @id @default(ulid())
quantity Decimal @db.Decimal(10, 0) quantity Decimal @db.Decimal(10, 0)
unit_type UnitType measure_unit_text String @db.VarChar(50)
measure_unit_code String @db.VarChar(50)
sku_code String @db.VarChar(50)
sku_vat Decimal @default(0.00) @db.Decimal(15, 2)
unit_price Decimal @default(0.00) @db.Decimal(15, 2) unit_price Decimal @default(0.00) @db.Decimal(15, 2)
total_amount Decimal @default(0.00) @db.Decimal(15, 2) total_amount Decimal @default(0.00) @db.Decimal(15, 2)
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
discount Decimal @default(0.00) @db.Decimal(15, 2) discount Decimal @default(0.00) @db.Decimal(15, 2)
notes String? @db.Text notes String? @db.Text
invoice_id String
good_id String?
service_id String?
payload Json? payload Json?
good_snapshot Json
invoice_id String
good_id String
service_id String?
discount_amount Decimal? @db.Decimal(15, 2)
tax_amount Decimal? @db.Decimal(15, 2)
good Good @relation(fields: [good_id], references: [id])
invoice SalesInvoice @relation(fields: [invoice_id], references: [id]) invoice SalesInvoice @relation(fields: [invoice_id], references: [id])
good Good? @relation(fields: [good_id], references: [id])
service Service? @relation(fields: [service_id], references: [id]) service Service? @relation(fields: [service_id], references: [id])
@@index([invoice_id, good_id]) @@index([invoice_id, good_id])
@@index([good_id], map: "sales_invoice_items_good_id_fkey")
@@index([service_id], map: "sales_invoice_items_service_id_fkey")
@@map("sales_invoice_items") @@map("sales_invoice_items")
} }
model SaleInvoiceTspAttempts {
id String @id @default(ulid())
attempt_no Int
status TspProviderResponseStatus
message String @db.Text
sent_at DateTime? @db.Timestamp(0)
received_at DateTime? @db.Timestamp(0)
created_at DateTime @default(now()) @db.Timestamp(0)
invoice_id String
provider_request_payload Json
raw_request_payload Json
error_message String? @db.Text
fiscal_warnings Json?
provider_response Json?
validation_errors Json?
invoice SalesInvoice @relation(fields: [invoice_id], references: [id], onDelete: Cascade)
@@unique([invoice_id, attempt_no])
@@index([status])
@@index([invoice_id])
@@map("sale_invoice_tsp_attempts")
}
model SalesInvoicePayment { model SalesInvoicePayment {
id String @id @default(ulid()) id String @id @default(ulid())
invoice_id String
amount Decimal @db.Decimal(15, 2) amount Decimal @db.Decimal(15, 2)
payment_method PaymentMethodType payment_method PaymentMethodType
paid_at DateTime paid_at DateTime @db.Timestamp(0)
created_at DateTime @default(now()) created_at DateTime @default(now()) @db.Timestamp(0)
invoice_id String
terminal_info SalesInvoicePaymentTerminalInfo?
invoice SalesInvoice @relation(fields: [invoice_id], references: [id]) invoice SalesInvoice @relation(fields: [invoice_id], references: [id])
@@index([invoice_id]) @@index([invoice_id])
@@map("sales_invoice_payments") @@map("sales_invoice_payments")
} }
model SalesInvoicePaymentTerminalInfo {
id String @id @default(ulid())
terminal_id String
stan String
rrn String
transaction_date_time DateTime
customer_card_no String?
description String?
created_at DateTime @default(now()) @db.Timestamp(0)
payment_id String @unique
payment SalesInvoicePayment @relation(fields: [payment_id], references: [id])
@@unique([terminal_id, stan, rrn, payment_id])
@@map("sales_invoice_payment_terminal_info")
}
+5 -7
View File
@@ -2,19 +2,18 @@ model Service {
id String @id @default(ulid()) id String @id @default(ulid())
name String @db.VarChar(255) name String @db.VarChar(255)
description String? @db.Text description String? @db.Text
sku String @unique() @db.VarChar(100) sku String @unique @db.VarChar(100)
local_sku String? @unique() @db.VarChar(100) local_sku String? @unique @db.VarChar(100)
barcode String? @unique() @db.VarChar(100) barcode String? @unique @db.VarChar(100)
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
deleted_at DateTime? @db.Timestamp(0) deleted_at DateTime? @db.Timestamp(0)
category_id String? category_id String?
base_sale_price Decimal @default(0.00) @db.Decimal(15, 0) base_sale_price Decimal @default(0) @db.Decimal(15, 0)
account_id String account_id String
complex_id String complex_id String
category ServiceCategory? @relation(fields: [category_id], references: [id])
sales_invoice_items SalesInvoiceItem[] sales_invoice_items SalesInvoiceItem[]
category ServiceCategory? @relation(fields: [category_id], references: [id])
@@index([category_id]) @@index([category_id])
@@map("services") @@map("services")
@@ -30,7 +29,6 @@ model ServiceCategory {
created_at DateTime @default(now()) @db.Timestamp(0) created_at DateTime @default(now()) @db.Timestamp(0)
updated_at DateTime @updatedAt @db.Timestamp(0) updated_at DateTime @updatedAt @db.Timestamp(0)
deleted_at DateTime? @db.Timestamp(0) deleted_at DateTime? @db.Timestamp(0)
services Service[] services Service[]
@@map("service_categories") @@map("service_categories")
+454 -291
View File
@@ -1,12 +1,15 @@
import { PasswordUtil } from '@/common/utils/password.util' import { PasswordUtil } from '@/common/utils/password.util'
import { generateTrackingCode } from '@/common/utils/tracking-code-generator.util' import { GoodPricingModel } from '@/generated/prisma/enums'
import { GoodPricingModel, UnitType } from '@/generated/prisma/enums' import { GoodCreateManyInput } from '@/generated/prisma/models'
import { GoodCreateInput, GoodCreateManyInput } from '@/generated/prisma/models' import { PrismaClient } from '../src/generated/prisma/client'
import { prisma } from '../src/lib/prisma' import { prismaAdapter } from '../src/lib/prisma'
const prisma = new PrismaClient({ adapter: prismaAdapter })
async function main() { async function main() {
const password = await PasswordUtil.hash('123456') const password = await PasswordUtil.hash('123456')
const adminUser = await prisma.admin.upsert({ await prisma.$transaction(async tx => {
const adminUser = await tx.admin.upsert({
where: { where: {
mobile_number: '09120258156', mobile_number: '09120258156',
}, },
@@ -16,7 +19,7 @@ async function main() {
last_name: 'حسنی', last_name: 'حسنی',
national_code: '0016022289', national_code: '0016022289',
mobile_number: '09120258156', mobile_number: '09120258156',
adminAccounts: { accounts: {
create: { create: {
account: { account: {
create: { create: {
@@ -31,33 +34,128 @@ async function main() {
}, },
}) })
const measureUnits = await tx.measureUnits.createMany({
data: [
{ code: '1', name: 'لنگه' },
{ code: '2', name: 'ثوب' },
{ code: '3', name: 'عدل' },
{ code: '4', name: 'نیم دوجین' },
{ code: '5', name: 'جعبه' },
{ code: '6', name: 'قرقره' },
{ code: '7', name: 'توپ' },
{ code: '8', name: 'کیلوگرم' },
{ code: '9', name: 'ست' },
{ code: '10', name: 'بطری' },
{ code: '11', name: 'دست' },
{ code: '12', name: 'برگ' },
{ code: '13', name: 'کارتن' },
{ code: '14', name: 'سطل' },
{ code: '15', name: 'عدد' },
{ code: '16', name: 'ورق' },
{ code: '17', name: 'بسته' },
{ code: '18', name: 'شاخه' },
{ code: '19', name: 'پاکت' },
{ code: '20', name: 'قوطی' },
{ code: '21', name: 'دستگاه' },
{ code: '22', name: 'جلد' },
{ code: '23', name: 'تخته' },
{ code: '24', name: 'تیوب' },
{ code: '25', name: 'رول' },
{ code: '26', name: 'متر' },
{ code: '27', name: 'طاقه' },
{ code: '28', name: 'کلاف' },
{ code: '29', name: 'جفت' },
{ code: '30', name: 'کیسه' },
{ code: '31', name: 'متر مربع' },
{ code: '32', name: 'طغرا' },
{ code: '33', name: 'پالت' },
{ code: '34', name: 'بشکه' },
{ code: '35', name: 'دوجین' },
{ code: '36', name: 'گالن' },
{ code: '37', name: '(رینگ)حلقه' },
{ code: '38', name: 'فاقد بسته بندی' },
{ code: '39', name: 'قراص' },
{ code: '40', name: 'کارتن' },
{ code: '41', name: 'قراصه' },
{ code: '42', name: 'صفحه' },
{ code: '43', name: 'لیتر' },
{ code: '44', name: 'مخزن' },
{ code: '45', name: 'ساشه' },
{ code: '46', name: 'تانکر' },
{ code: '47', name: 'کپسول' },
{ code: '48', name: 'دبه' },
{ code: '49', name: 'بندیل' },
{ code: '50', name: 'سبد' },
{ code: '51', name: '(رول)حلقه' },
{ code: '52', name: 'تن' },
{ code: '53', name: 'قالب' },
{ code: '54', name: 'بانکه' },
{ code: '55', name: 'شانه' },
{ code: '56', name: 'سیلندر' },
{ code: '57', name: 'متر مکعب' },
{ code: '58', name: 'فوت مربع' },
{ code: '59', name: 'حلب' },
{ code: '60', name: 'شیت' },
{ code: '61', name: 'چلیک' },
{ code: '62', name: 'جام' },
{ code: '63', name: 'گرم' },
{ code: '64', name: 'نخ' },
{ code: '65', name: 'شعله' },
{ code: '66', name: 'قیراط' },
{ code: '67', name: 'میلی لیتر' },
{ code: '68', name: 'میلی متر' },
{ code: '69', name: 'میلی گرم' },
{ code: '70', name: 'ساعت' },
{ code: '71', name: 'روز' },
{ code: '72', name: 'تن کیلومتر' },
{ code: '73', name: 'کیلووات ساعت' },
{ code: '74', name: 'نفر' },
{ code: '75', name: 'ثانیه' },
{ code: '76', name: 'دقیقه' },
{ code: '77', name: 'ماه' },
{ code: '78', name: 'سال' },
{ code: '79', name: 'قطعه' },
{ code: '80', name: 'سانتی متر' },
{ code: '81', name: 'سانتی متر مربع' },
{ code: '82', name: 'فروند' },
{ code: '83', name: 'واحد' },
{ code: '84', name: 'لیوان' },
{ code: '85', name: 'نوبت)`,' },
],
skipDuplicates: true,
})
const geramMeasureUnit = await tx.measureUnits.findFirst({
where: {
code: '63',
},
})
// ****************** GUILD Start ****************** // // ****************** GUILD Start ****************** //
const guilds = await prisma.guild.findMany() let guilds = await tx.guild.findMany()
if (!guilds.length) { if (!guilds.length) {
await prisma.guild.createMany({ await tx.guild.createMany({
data: [ data: [
{ {
name: 'طلا', name: 'طلا',
code: 'Gold', code: 'Gold',
}, invoice_template: 'GOLD_JEWELRY',
{
name: 'میوه و تره‌بار',
code: 'Fruit',
}, },
], ],
}) })
guilds = await tx.guild.findMany()
} }
// ****************** GUILD Good Categories Start ****************** // // ****************** GUILD Good Categories Start ****************** //
const goldGuild = await prisma.guild.findFirst({ const goldGuild = await tx.guild.findFirst({
where: { where: {
name: 'طلا', code: 'Gold',
}, },
}) })
const goldGuildId = goldGuild?.id const goldGuildId = goldGuild?.id
const goldGuildGoodCategories = await prisma.goodCategory.findMany({ const goldGuildGoodCategories = await tx.goodCategory.findMany({
where: { where: {
guild_id: goldGuildId, guild_id: goldGuildId,
is_default_guild_good: true, is_default_guild_good: true,
@@ -71,40 +169,59 @@ async function main() {
is_default_guild_good: true, is_default_guild_good: true,
}) })
await prisma.goodCategory.createMany({ await tx.goodCategory.createMany({
data: [ data: [
categoryFactory('زیورآلات'), categoryFactory('زیورآلات'),
categoryFactory('طلا'), // categoryFactory('طلا'),
categoryFactory('سایر'), // categoryFactory('سایر'),
categoryFactory('سکه'), // categoryFactory('سکه'),
categoryFactory('شمش'), // categoryFactory('شمش'),
categoryFactory('شمش'), // categoryFactory('شمش'),
], ],
}) })
} }
const measureUnit = await tx.measureUnits.findFirst({
where: {
code: '63',
},
})
if (measureUnit) {
// ****************** GUILD Good Start ****************** // // ****************** GUILD Good Start ****************** //
const goodFactory = ( async function goodFactory(
name: string, name: string,
categoryId: string, categoryId: string,
pricingModel: GoodPricingModel, pricingModel: GoodPricingModel,
unitType: UnitType, sku_code: string,
): GoodCreateManyInput => ({ measure_unit_code: string,
): Promise<GoodCreateManyInput | null> {
const sku = await tx.stockKeepingUnits.findUnique({
where: {
code: sku_code,
},
})
if (sku) {
return {
name, name,
category_id: categoryId, category_id: categoryId,
pricing_model: pricingModel, pricing_model: pricingModel,
sku: '', sku_id: sku.id,
unit_type: unitType, measure_unit_id: measureUnit!.id,
is_default_guild_good: true, is_default_guild_good: true,
}) }
}
return null
}
const zivarCategory = await prisma.goodCategory.findFirst({ const zivarCategory = await tx.goodCategory.findFirst({
where: { where: {
name: 'زیورآلات', name: 'زیورآلات',
}, },
}) })
if (zivarCategory) { if (zivarCategory) {
const zivarGoods = await prisma.good.count({ const zivarGoods = await tx.good.count({
where: { where: {
category_id: zivarCategory?.id, category_id: zivarCategory?.id,
}, },
@@ -112,311 +229,356 @@ async function main() {
if (!zivarGoods) { if (!zivarGoods) {
const goodItems: GoodCreateManyInput[] = [] const goodItems: GoodCreateManyInput[] = []
goodItems.push( const preparedGoodItems = [
...[ await goodFactory(
goodFactory(
'آویز گردنبند طلا', 'آویز گردنبند طلا',
zivarCategory.id, zivarCategory.id,
GoodPricingModel.GOLD, GoodPricingModel.GOLD,
UnitType.GRAM, '2720000044696',
geramMeasureUnit!.id,
), ),
goodFactory('النگو', zivarCategory.id, GoodPricingModel.GOLD, UnitType.GRAM), await goodFactory(
goodFactory('انگشتر', zivarCategory.id, GoodPricingModel.GOLD, UnitType.GRAM), 'النگو',
goodFactory('دست‌بند', zivarCategory.id, GoodPricingModel.GOLD, UnitType.GRAM), zivarCategory.id,
goodFactory('زنجیر', zivarCategory.id, GoodPricingModel.GOLD, UnitType.GRAM),
goodFactory('سرویس', zivarCategory.id, GoodPricingModel.GOLD, UnitType.GRAM),
goodFactory('گوشواره', zivarCategory.id, GoodPricingModel.GOLD, UnitType.GRAM),
],
)
await prisma.good.createMany({ data: goodItems })
}
}
const goldCategory = await prisma.goodCategory.findFirst({
where: {
name: 'طلا',
},
})
if (goldCategory) {
const goldGoods = await prisma.good.count({
where: {
category_id: goldCategory?.id,
},
})
if (!goldGoods) {
const goodItems: GoodCreateInput[] = []
goodItems.push(
...[
goodFactory(
'طلای آب شده',
goldCategory.id,
GoodPricingModel.GOLD, GoodPricingModel.GOLD,
UnitType.GRAM, '2720000044665',
geramMeasureUnit!.id,
), ),
goodFactory( await goodFactory(
'طلای شکسته', 'انگشتر',
goldCategory.id, zivarCategory.id,
GoodPricingModel.GOLD, GoodPricingModel.GOLD,
UnitType.GRAM, '2720000044672',
geramMeasureUnit!.id,
), ),
goodFactory( await goodFactory(
'طلای مستعمل', 'دست‌بند',
goldCategory.id, zivarCategory.id,
GoodPricingModel.GOLD, GoodPricingModel.GOLD,
UnitType.GRAM, '2720000044689',
geramMeasureUnit!.id,
), ),
], await goodFactory(
) 'زنجیر',
await prisma.good.createMany({ data: goodItems }) zivarCategory.id,
GoodPricingModel.GOLD,
'2720000044702',
geramMeasureUnit!.id,
),
await goodFactory(
'سرویس',
zivarCategory.id,
GoodPricingModel.GOLD,
'2720000044733',
geramMeasureUnit!.id,
),
await goodFactory(
'گوشواره',
zivarCategory.id,
GoodPricingModel.GOLD,
'2720000044726',
geramMeasureUnit!.id,
),
].filter((item): item is GoodCreateManyInput => item !== null)
goodItems.push(...preparedGoodItems)
await tx.good.createMany({ data: goodItems })
} }
} }
const coinCategory = await prisma.goodCategory.findFirst({ // const goldCategory = await tx.goodCategory.findFirst({
where: { // where: {
name: 'سکه', // name: 'طلا',
}, // },
}) // })
if (coinCategory) { // if (goldCategory) {
const coinGoods = await prisma.good.count({ // const goldGoods = await tx.good.count({
where: { // where: {
category_id: coinCategory?.id, // category_id: goldCategory?.id,
}, // },
}) // })
if (!coinGoods) { // if (!goldGoods) {
const goodItems: GoodCreateInput[] = [] // const goodItems: GoodCreateInput[] = []
goodItems.push( // goodItems.push(
...[ // ...[
goodFactory( // goodFactory(
'مسکوکات خارجی', // 'طلای آب شده',
coinCategory.id, // goldCategory.id,
GoodPricingModel.STANDARD, // GoodPricingModel.GOLD,
UnitType.COUNT, // UnitType.GRAM,
), // ),
goodFactory( // goodFactory(
'مسکوکات داخلی (پارسیان)', // 'طلای شکسته',
coinCategory.id, // goldCategory.id,
GoodPricingModel.STANDARD, // GoodPricingModel.GOLD,
UnitType.COUNT, // UnitType.GRAM,
), // ),
goodFactory( // goodFactory(
'تمام بهار آزادی (طرح جدید)', // 'طلای مستعمل',
coinCategory.id, // goldCategory.id,
GoodPricingModel.STANDARD, // GoodPricingModel.GOLD,
UnitType.COUNT, // UnitType.GRAM,
), // ),
goodFactory( // ],
'تمام بهار آزادی (طرح قدیم)', // )
coinCategory.id, // await tx.good.createMany({ data: goodItems })
GoodPricingModel.STANDARD, // }
UnitType.COUNT, // }
),
],
)
await prisma.good.createMany({ data: goodItems })
}
}
const shemshCategory = await prisma.goodCategory.findFirst({ // const coinCategory = await tx.goodCategory.findFirst({
where: { // where: {
name: 'شمش', // name: 'سکه',
}, // },
}) // })
// if (coinCategory) {
// const coinGoods = await tx.good.count({
// where: {
// category_id: coinCategory?.id,
// },
// })
// if (!coinGoods) {
// const goodItems: GoodCreateInput[] = []
// goodItems.push(
// ...[
// goodFactory(
// 'مسکوکات خارجی',
// coinCategory.id,
// GoodPricingModel.STANDARD,
// UnitType.COUNT,
// ),
// goodFactory(
// 'مسکوکات داخلی (پارسیان)',
// coinCategory.id,
// GoodPricingModel.STANDARD,
// UnitType.COUNT,
// ),
// goodFactory(
// 'تمام بهار آزادی (طرح جدید)',
// coinCategory.id,
// GoodPricingModel.STANDARD,
// UnitType.COUNT,
// ),
// goodFactory(
// 'تمام بهار آزادی (طرح قدیم)',
// coinCategory.id,
// GoodPricingModel.STANDARD,
// UnitType.COUNT,
// ),
// ],
// )
// await tx.good.createMany({ data: goodItems })
// }
// }
if (shemshCategory) { // const shemshCategory = await tx.goodCategory.findFirst({
const shemshGoods = await prisma.good.count({ // where: {
where: { // name: 'شمش',
category_id: shemshCategory?.id, // },
}, // })
})
if (!shemshGoods) { // if (shemshCategory) {
await prisma.good.create({ // const shemshGoods = await tx.good.count({
data: goodFactory( // where: {
'شمش استاندارد', // category_id: shemshCategory?.id,
shemshCategory.id, // },
GoodPricingModel.GOLD, // })
UnitType.GRAM, // if (!shemshGoods) {
), // await tx.good.create({
}) // data: goodFactory(
} // 'شمش استاندارد',
// shemshCategory.id,
// GoodPricingModel.GOLD,
// UnitType.GRAM,
// ),
// })
// }
// }
} }
// ****************** partner Start ****************** // // ****************** partner Start ****************** //
let partner = await prisma.partner.findFirst() // let partner = await tx.partner.findFirst()
let license = await prisma.license.findFirst() // let license = await tx.license.findFirst()
if (!partner) { // if (!partner) {
partner = await prisma.partner.create({ // partner = await tx.partner.create({
data: { // data: {
name: 'تیس', // name: 'تیس',
code: 'TIS', // code: 'TIS',
status: 'ACTIVE', // status: 'ACTIVE',
partner_accounts: { // tsp_provider: TspProviderType.NAMA,
create: { // accounts: {
role: 'OWNER', // create: {
account: { // role: 'OWNER',
create: { // account: {
username: 'tis', // create: {
password: await PasswordUtil.hash('123456'), // username: 'tis',
status: 'ACTIVE', // password: await PasswordUtil.hash('123456'),
type: 'PARTNER', // status: 'ACTIVE',
}, // type: 'PARTNER',
}, // },
}, // },
}, // },
}, // },
}) // },
} // })
if (partner && !license) { // }
await prisma.$transaction(async tx => { // if (partner && !license) {
const startOfToday = new Date() // await tx.$transaction(async tx => {
startOfToday.setHours(0, 0, 0, 0) // const startOfToday = new Date()
// startOfToday.setHours(0, 0, 0, 0)
const month = startOfToday.getMonth() // const month = startOfToday.getMonth()
let year = startOfToday.getFullYear() // let year = startOfToday.getFullYear()
let expMonth = month + 3 // let expMonth = month + 3
if (expMonth > 11) {
expMonth = expMonth - 11
year = year + 1
}
startOfToday.setFullYear(year)
startOfToday.setMonth(expMonth)
const transaction = await tx.licenseChargeTransaction.create({
data: {
activation_expires_at: startOfToday,
purchased_count: 1,
tracking_code: generateTrackingCode('LIC', 6),
partner: {
connect: {
id: partner.id,
},
},
},
select: {
id: true,
},
})
if (transaction)
license = await tx.license.create({
data: {
charge_transaction: {
connect: {
id: transaction.id,
},
},
},
})
})
}
// ****************** BA Start ****************** //
const ba = await prisma.businessActivity.count()
if (!ba && license) {
const startOfToday = new Date()
startOfToday.setHours(0, 0, 0, 0)
let year = startOfToday.getFullYear()
let expYear = year + 1
// if (expMonth > 11) { // if (expMonth > 11) {
// expMonth = expMonth - 11 // expMonth = expMonth - 11
// year = year + 1 // year = year + 1
// } // }
startOfToday.setFullYear(expYear) // startOfToday.setFullYear(year)
// startOfToday.setMonth(expMonth)
await prisma.businessActivity.create({
data: {
name: 'طلا فروشی',
economic_code: '0111111111',
license_activation: {
create: {
expires_at: startOfToday,
starts_at: new Date(),
license: {
connect: {
id: license.id,
},
},
},
},
consumer: {
create: {
first_name: 'محمد',
last_name: 'زرگر',
mobile_number: '09120258155',
national_code: '1234567890',
consumer_accounts: {
create: {
role: 'OWNER',
account: {
create: {
username: 'zargar',
password: await PasswordUtil.hash('123456'),
status: 'ACTIVE',
type: 'CONSUMER',
},
},
},
},
partner: {
connect: {
id: partner.id,
},
},
},
},
guild: {
connect: {
id: guilds[0].id,
},
},
complexes: {
create: {
name: 'فروشگاه طلای مرکزی',
address: 'تهران، خیابان جمهوری',
branch_code: '12332',
// pos_list: {
// create: {
// name: 'لاین ۱',
// pos_type: POSType.WEB,
// status: 'ACTIVE',
// account: {
// create: {
// role: 'OPERATOR',
// account_allocation: {
// const transaction = await tx.licenseChargeTransaction.create({
// data: {
// activation_expires_at: startOfToday,
// purchased_count: 1,
// tracking_code: generateTrackingCode('LIC', 6),
// partner: {
// connect: {
// id: partner.id,
// }, // },
// },
// },
// select: {
// id: true,
// },
// })
// if (transaction)
// license = await tx.license.create({
// data: {
// charge_transaction: {
// connect: {
// id: transaction.id,
// },
// },
// },
// })
// })
// }
// // ****************** BA Start ****************** //
// const ba = await tx.businessActivity.count()
// if (!ba && license) {
// const startOfToday = new Date()
// startOfToday.setHours(0, 0, 0, 0)
// let year = startOfToday.getFullYear()
// let expYear = year + 1
// // if (expMonth > 11) {
// // expMonth = expMonth - 11
// // year = year + 1
// // }
// startOfToday.setFullYear(expYear)
// await tx.businessActivity.create({
// data: {
// name: 'طلا فروشی',
// economic_code: '0111111111',
// fiscal_id: '0111111111',
// partner_token: 'TIS-BA-001',
// license_activation: {
// create: {
// expires_at: startOfToday,
// starts_at: new Date(),
// license: {
// connect: {
// id: license.id,
// },
// },
// },
// },
// consumer: {
// create: {
// type: ConsumerType.INDIVIDUAL,
// individual: {
// create: {
// first_name: 'محمد',
// last_name: 'زرگر',
// mobile_number: '09120258155',
// national_code: '1234567890',
// partner: {
// connect: {
// id: partner.id,
// },
// },
// },
// },
// accounts: {
// create: {
// role: 'OWNER',
// account: { // account: {
// create: { // create: {
// username: 'line1', // username: 'zargar',
// password: await PasswordUtil.hash('123456'), // password: await PasswordUtil.hash('123456'),
// status: 'ACTIVE', // status: 'ACTIVE',
// type: 'CONSUMER', // type: 'CONSUMER',
// }, // },
// }, // },
// }
// }
// }, // },
// }, // },
}, // },
}, // },
}, // guild: {
}) // connect: {
} // id: guilds[0].id,
// },
// },
// complexes: {
// create: {
// name: 'فروشگاه طلای مرکزی',
// address: 'تهران، خیابان جمهوری',
// branch_code: '12332',
// // pos_list: {
// // create: {
// // name: 'لاین ۱',
// // pos_type: POSType.WEB,
// // status: 'ACTIVE',
// // account: {
// // create: {
// // role: 'OPERATOR',
// // account_allocation: {
// // },
// // account: {
// // create: {
// // username: 'line1',
// // password: await PasswordUtil.hash('123456'),
// // status: 'ACTIVE',
// // type: 'CONSUMER',
// // },
// // },
// // }
// // }
// // },
// // },
// },
// },
// },
// })
// }
// ****************** BA Start ****************** // // ****************** BA Start ****************** //
// ****************** provider Start ****************** // // ****************** provider Start ****************** //
const provider = await prisma.provider.count() const provider = await tx.provider.count()
if (!provider) { if (!provider) {
await prisma.provider.create({ await tx.provider.create({
data: { data: {
name: 'توسن', name: 'توسن',
code: 'Tosan', code: 'Tosan',
status: 'ACTIVE', status: 'ACTIVE',
provider_accounts: { accounts: {
create: { create: {
role: 'OWNER', role: 'OWNER',
account: { account: {
@@ -432,6 +594,7 @@ async function main() {
}, },
}) })
} }
})
} }
main() main()
-548
View File
@@ -1,548 +0,0 @@
-- AUTO-GENERATED MYSQL TRIGGER DUMP
-- Generated at: 2026-01-06T16:09:38.959Z
-- ------------------------------------------
-- index: 1
-- Trigger: trg_bank_account_transaction_after_delete
-- Event: DELETE
-- Table: Bank_Account_Transactions
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_bank_account_transaction_after_delete`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_bank_account_transaction_after_delete` AFTER DELETE ON `Bank_Account_Transactions` FOR EACH ROW BEGIN
UPDATE `Bank_Account_Balance` SET balance = balance - OLD.amount WHERE `bankAccountId` = OLD.bankAccountId;
END;
-- ------------------------------------------
-- index: 2
-- Trigger: trg_transfer_item_after_insert
-- Event: INSERT
-- Table: Inventory_Transfer_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_transfer_item_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_transfer_item_after_insert` AFTER INSERT ON `Inventory_Transfer_Items` FOR EACH ROW begin
DECLARE fromInv INT;
DECLARE toInv INT;
DECLARE _avgCost DECIMAL(10,2);
DECLARE latestQuantityInOrigin DECIMAL(10,2);
DECLARE latestQuantityInDestination DECIMAL(10,2);
SELECT fromInventoryId, toInventoryId INTO fromInv, toInv
FROM Inventory_Transfers WHERE id = NEW.transferId;
SELECT COALESCE(avgCost, 0), COALESCE(quantity, 0) INTO _avgCost, latestQuantityInOrigin FROM Stock_Balance
WHERE ProductId = NEW.productId AND inventoryId = fromInv LIMIT 1;
SELECT COALESCE(quantity, 0) INTO latestQuantityInDestination FROM Stock_Balance
WHERE ProductId = NEW.productId AND inventoryId = toInv LIMIT 1;
-- OUT from source
INSERT INTO Stock_Movements
(type, quantity, unitPrice, totalCost, avgCost, referenceType, referenceId, productId, inventoryId, counterInventoryId, createdAt, remainedInStock)
VALUES
('OUT', NEW.count, _avgCost, _avgCost*NEW.count, _avgCost, 'INVENTORY_TRANSFER', NEW.transferId, NEW.productId, fromInv, toInv, NOW(), latestQuantityInOrigin-NEW.count);
-- IN to destination
INSERT INTO Stock_Movements
(type, quantity, unitPrice, totalCost, avgCost, referenceType, referenceId, productId, inventoryId, counterInventoryId, createdAt, remainedInStock)
VALUES
('IN', NEW.count,_avgCost, _avgCost*NEW.count,_avgCost, 'INVENTORY_TRANSFER', NEW.transferId, NEW.productId, toInv, fromInv, NOW(), latestQuantityInOrigin-NEW.count);
end;
-- ------------------------------------------
-- index: 3
-- Trigger: trg_order_item_after_update
-- Event: UPDATE
-- Table: Order_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_order_item_after_update`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_order_item_after_update` AFTER UPDATE ON `Order_Items` FOR EACH ROW BEGIN
UPDATE Stock_Reservations
SET quantity = quantity - OLD.quantity + NEW.quantity
WHERE orderId = NEW.orderId AND productId = NEW.productId;
END;
-- ------------------------------------------
-- index: 4
-- Trigger: trg_order_item_after_delete
-- Event: DELETE
-- Table: Order_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_order_item_after_delete`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_order_item_after_delete` AFTER DELETE ON `Order_Items` FOR EACH ROW BEGIN
DELETE From Stock_Reservations
WHERE orderId = OLD.orderId AND productId = OLD.productId;
END;
-- ------------------------------------------
-- index: 5
-- Trigger: trg_order_after_cancel
-- Event: UPDATE
-- Table: Orders
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_order_after_cancel`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_order_after_cancel` AFTER UPDATE ON `Orders` FOR EACH ROW BEGIN
IF NEW.status = 'CANCELED' OR NEW.status = 'REJECTED' OR NEW.status = 'DONE' THEN
UPDATE Stock_Reservations sr SET quantity = 0
WHERE sr.orderId = NEW.id;
END IF;
END;
-- ------------------------------------------
-- index: 6
-- Trigger: trg_purchase_receipt_item_after_insert
-- Event: INSERT
-- Table: Purchase_Receipt_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_purchase_receipt_item_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_purchase_receipt_item_after_insert` AFTER INSERT ON `Purchase_Receipt_Items` FOR EACH ROW BEGIN DECLARE latestQuantity DECIMAL(10, 2) DEFAULT 0;
DECLARE invId INT;
DECLARE suppId INT;
-- Get inventory & supplier from
SELECT inventoryId, supplierId
INTO invId, suppId
FROM Purchase_Receipts
WHERE id = NEW.receiptId;
-- Get current stock quantity (if exists)
SELECT COALESCE(quantity, 0)
INTO latestQuantity
FROM Stock_Balance sb
WHERE sb.inventoryId = invId
AND sb.productId = NEW.productId
LIMIT 1;
-- Insert stock movement
INSERT INTO Stock_Movements (
type,
quantity,
unitPrice,
totalCost,
referenceType,
referenceId,
productId,
inventoryId,
avgCost,
supplierId,
remainedInStock,
createdAt
)
VALUES (
'IN',
NEW.count,
NEW.unitPrice,
NEW.totalAmount,
'PURCHASE',
NEW.receiptId,
NEW.productId,
invId,
CASE
WHEN NEW.count = 0 THEN 0
ELSE NEW.totalAmount / NEW.count
END
,
suppId,
latestQuantity + NEW.count,
NOW()
);
END;
-- ------------------------------------------
-- index: 7
-- Trigger: trg_pr_payment_after_delete
-- Event: DELETE
-- Table: Purchase_Receipt_Payments
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_pr_payment_after_delete`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_pr_payment_after_delete` AFTER DELETE ON `Purchase_Receipt_Payments` FOR EACH ROW BEGIN
DECLARE receiptTotal DECIMAL(14,2);
DECLARE newPaid DECIMAL(14,2);
SELECT totalAmount, paidAmount
INTO receiptTotal, newPaid
FROM Purchase_Receipts
WHERE id = OLD.receiptId
FOR UPDATE;
IF OLD.type = 'PAYMENT' THEN
SET newPaid = newPaid - OLD.amount;
ELSE
SET newPaid = newPaid + OLD.amount;
END IF;
UPDATE Purchase_Receipts
SET
paidAmount = newPaid,
status =
CASE
WHEN newPaid = 0 THEN 'UNPAID'
WHEN newPaid < receiptTotal THEN 'PARTIALLY_PAID'
ELSE 'PAID'
END
WHERE id = OLD.receiptId;
END;
-- ------------------------------------------
-- index: 8
-- Trigger: trg_sales_invoice_items_before_insert
-- Event: INSERT
-- Table: Sales_Invoice_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_sales_invoice_items_before_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_sales_invoice_items_before_insert` BEFORE INSERT ON `Sales_Invoice_Items` FOR EACH ROW BEGIN
DECLARE current_stock DECIMAL(10, 2);
DECLARE inventory_id INT;
SELECT pa.inventoryId INTO inventory_id
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = NEW.invoiceId;
SELECT COALESCE(sav.availableQuantity, 0) INTO current_stock
FROM Stock_Available_View sav
WHERE productId = NEW.productId AND sav.inventoryId = inventory_id;
IF NEW.count > current_stock THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'موجودی کالا در انبار کافی نیست.';
END IF;
end;
-- ------------------------------------------
-- index: 9
-- Trigger: trg_sales_invoice_items_after_insert
-- Event: INSERT
-- Table: Sales_Invoice_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_sales_invoice_items_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_sales_invoice_items_after_insert` AFTER INSERT ON `Sales_Invoice_Items` FOR EACH ROW begin DECLARE current_stock DECIMAL(10, 2);
DECLARE inventory_id INT;
DECLARE customer_id INT;
DECLARE pos_id INT;
SELECT posAccountId, customerId INTO pos_id, customer_id
FROM Sales_Invoices si
WHERE si.id = NEW.invoiceId
LIMIT 1;
INSERT INTO Trigger_Logs (name , message) VALUES ('pos_id', pos_id);
INSERT INTO Trigger_Logs (name , message) VALUES ('customer_id', customer_id);
SELECT pa.inventoryId INTO inventory_id
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = NEW.invoiceId;
SELECT COALESCE(quantity, 0) INTO current_stock
FROM Stock_Balance sb
WHERE productId = NEW.productId AND sb.inventoryId = inventory_id
LIMIT 1;
INSERT INTO Stock_Movements (
type,
quantity,
unitPrice,
totalCost,
referenceType,
referenceId,
productId,
inventoryId,
avgCost,
remainedInStock,
customerId,
createdAt
)
VALUES (
'OUT',
NEW.count,
NEW.unitPrice,
NEW.totalAmount,
'SALES',
NEW.invoiceId,
NEW.productId,
inventory_id,
CASE
WHEN NEW.count = 0 THEN 0
ELSE NEW.totalAmount / NEW.count
END,
current_stock - NEW.count,
customer_id,
NOW()
);
END;
-- ------------------------------------------
-- index: 10
-- Trigger: trg_pos_account_payment_after_insert
-- Event: INSERT
-- Table: Sales_Invoice_Payments
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_pos_account_payment_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_pos_account_payment_after_insert` AFTER INSERT ON `Sales_Invoice_Payments` FOR EACH ROW BEGIN
DECLARE _bankAccountId INT;
IF(NEW.paymentMethod != 'CASH') THEN
SELECT cashBankAccountId INTO _bankAccountId
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = NEW.invoiceId;
End IF;
INSERT INTO Bank_Account_Transactions (
bankAccountId,
type,
amount,
balanceAfter,
referenceType,
referenceId
)
VALUES(
_bankAccountId,
'DEPOSIT',
NEW.amount,
0,
'POS_SALE',
NEW.id
);
END;
-- ------------------------------------------
-- index: 11
-- Trigger: trg_sales_invoice_payment_after_insert
-- Event: INSERT
-- Table: Sales_Invoice_Payments
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_sales_invoice_payment_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_sales_invoice_payment_after_insert` AFTER INSERT ON `Sales_Invoice_Payments` FOR EACH ROW BEGIN
DECLARE currentBalance DECIMAL(15,2);
DECLARE bankAccountId INT;
SELECT pa.bankAccountId INTO bankAccountId
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = NEW.invoiceId;
SELECT balance INTO currentBalance
FROM Bank_Account_Balance
WHERE bankAccountId = bankAccountId
FOR UPDATE;
IF currentBalance IS NULL THEN
SET currentBalance = 0;
INSERT INTO Bank_Account_Balance (bankAccountId, balance)
VALUES (bankAccountId, 0);
END IF;
SET currentBalance = currentBalance + NEW.amount;
INSERT INTO Bank_Account_Transactions
(bankAccountId, type, amount, balanceAfter, referenceType, referenceId)
VALUES
(bankAccountId, 'DEPOSIT', NEW.amount, currentBalance, 'POS_SALE', NEW.id);
UPDATE Bank_Account_Balance
SET balance = currentBalance
WHERE bankAccountId = bankAccountId;
END;
-- ------------------------------------------
-- index: 12
-- Trigger: trg_stock_sale_insert
-- Event: INSERT
-- Table: Stock_Movements
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_stock_sale_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_stock_sale_insert` AFTER INSERT ON `Stock_Movements` FOR EACH ROW BEGIN IF NEW.referenceType = 'SALES' THEN
INSERT INTO
Stock_Balance (
productId,
quantity,
avgCost,
totalCost,
inventoryId,
updatedAt
)
VALUES (
NEW.productId,
NEW.quantity,
NEW.unitPrice,
NEW.totalCost,
NEW.inventoryId,
NOW()
)
ON DUPLICATE KEY UPDATE
quantity = quantity - NEW.quantity,
totalCost = totalCost - NEW.totalCost,
avgCost = totalCost / quantity;
END IF;
END;
-- ------------------------------------------
-- index: 13
-- Trigger: trg_stock_purchase_insert
-- Event: INSERT
-- Table: Stock_Movements
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_stock_purchase_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_stock_purchase_insert` AFTER INSERT ON `Stock_Movements` FOR EACH ROW BEGIN IF NEW.referenceType = 'PURCHASE' THEN
INSERT INTO
Stock_Balance (
productId,
quantity,
avgCost,
totalCost,
inventoryId,
updatedAt
)
VALUES (
NEW.productId,
NEW.quantity,
NEW.unitPrice,
NEW.totalCost,
NEW.inventoryId,
NOW()
)
ON DUPLICATE KEY UPDATE
quantity = quantity + NEW.quantity,
totalCost = totalCost + NEW.totalCost,
avgCost = totalCost / quantity;
END IF;
END;
-- ------------------------------------------
-- index: 14
-- Trigger: trg_stock_transfer
-- Event: INSERT
-- Table: Stock_Movements
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_stock_transfer`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_stock_transfer` AFTER INSERT ON `Stock_Movements` FOR EACH ROW BEGIN IF NEW.referenceType = 'INVENTORY_TRANSFER' THEN IF NEW.type = 'IN' THEN
INSERT INTO
Stock_Balance (
productId,
inventoryId,
quantity,
totalCost,
avgCost,
updatedAt
)
VALUES (
NEW.productId,
NEW.inventoryId,
NEW.quantity,
NEW.totalCost,
CASE
WHEN NEW.quantity = 0 THEN 0
ELSE NEW.totalCost / NEW.quantity
END,
NOW()
)
ON DUPLICATE KEY UPDATE
quantity = quantity + NEW.quantity,
totalCost = totalCost + NEW.totalCost,
avgCost = CASE
WHEN (quantity + NEW.quantity) = 0 THEN 0
ELSE (totalCost + NEW.totalCost) / (quantity + NEW.quantity)
END,
updatedAt = NOW();
END IF;
IF NEW.type = 'OUT' THEN IF EXISTS (
SELECT 1
FROM Stock_Balance sb
WHERE
sb.productId = NEW.productId
AND sb.inventoryId = NEW.inventoryId
) THEN
UPDATE Stock_Balance sb
SET
sb.quantity = sb.quantity - NEW.quantity,
sb.totalCost = sb.totalCost - (sb.avgCost * NEW.quantity),
sb.updatedAt = NOW()
WHERE
sb.productId = NEW.productId
AND sb.inventoryId = NEW.inventoryId;
ELSE
INSERT INTO
Stock_Balance (
productId,
inventoryId,
quantity,
totalCost,
avgCost,
updatedAt
)
VALUES (
NEW.productId,
NEW.inventoryId,
- NEW.quantity,
- COALESCE(NEW.unitPrice, 0) * NEW.quantity,
COALESCE(NEW.unitPrice, 0),
NOW()
);
END IF;
END IF;
END IF;
END;
-- ------------------------------------------
-- index: 15
-- Trigger: trg_no_negative_available_stock
-- Event: INSERT
-- Table: Stock_Reservations
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_no_negative_available_stock`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_no_negative_available_stock` BEFORE INSERT ON `Stock_Reservations` FOR EACH ROW BEGIN
DECLARE available DECIMAL(14,3);
SELECT availableQuantity
INTO available
FROM Stock_Available_View
WHERE productId = NEW.productId
AND inventoryId = NEW.inventoryId;
IF available < NEW.quantity THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'موجودی کافی نیست';
END IF;
END;
-825
View File
@@ -1,825 +0,0 @@
-- AUTO-GENERATED MYSQL TRIGGER DUMP
-- Generated at: 2026-01-04T09:46:30.365Z
-- ------------------------------------------
-- index: 1
-- Trigger: trg_bank_account_transaction_after_insert
-- Event: INSERT
-- Table: Bank_Account_Transactions
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_bank_account_transaction_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_bank_account_transaction_after_insert` AFTER INSERT ON `Bank_Account_Transactions` FOR EACH ROW BEGIN
IF NEW.type = 'DEPOSIT' THEN
UPDATE Bank_Account_Balance SET balance = balance + NEW.amount WHERE bankAccountId = NEW.bankAccountId;
ELSEIF NEW.type = 'WITHDRAWAL' THEN
UPDATE Bank_Account_Balance SET balance = balance - NEW.amount WHERE bankAccountId = NEW.bankAccountId;
END IF;
END;
-- ------------------------------------------
-- index: 2
-- Trigger: trg_bank_account_transaction_after_delete
-- Event: DELETE
-- Table: Bank_Account_Transactions
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_bank_account_transaction_after_delete`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_bank_account_transaction_after_delete` AFTER DELETE ON `Bank_Account_Transactions` FOR EACH ROW BEGIN
UPDATE Bank_Accounts SET balance = balance - OLD.amount;
END;
-- ------------------------------------------
-- index: 3
-- Trigger: trg_transfer_item_after_insert
-- Event: INSERT
-- Table: Inventory_Transfer_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_transfer_item_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_transfer_item_after_insert` AFTER INSERT ON `Inventory_Transfer_Items` FOR EACH ROW begin
DECLARE fromInv INT;
DECLARE toInv INT;
DECLARE _avgCost DECIMAL(10,2);
DECLARE latestQuantityInOrigin DECIMAL(10,2);
DECLARE latestQuantityInDestination DECIMAL(10,2);
SELECT fromInventoryId, toInventoryId INTO fromInv, toInv
FROM Inventory_Transfers WHERE id = NEW.transferId;
SELECT COALESCE(avgCost, 0), COALESCE(quantity, 0) INTO _avgCost, latestQuantityInOrigin FROM Stock_Balance
WHERE ProductId = NEW.productId AND inventoryId = fromInv LIMIT 1;
SELECT COALESCE(quantity, 0) INTO latestQuantityInDestination FROM Stock_Balance
WHERE ProductId = NEW.productId AND inventoryId = toInv LIMIT 1;
-- OUT from source
INSERT INTO Stock_Movements
(type, quantity, unitPrice, totalCost, avgCost, referenceType, referenceId, productId, inventoryId, counterInventoryId, createdAt, remainedInStock)
VALUES
('OUT', NEW.count, _avgCost, _avgCost*NEW.count, _avgCost, 'INVENTORY_TRANSFER', NEW.transferId, NEW.productId, fromInv, toInv, NOW(), latestQuantityInOrigin-NEW.count);
-- IN to destination
INSERT INTO Stock_Movements
(type, quantity, unitPrice, totalCost, avgCost, referenceType, referenceId, productId, inventoryId, counterInventoryId, createdAt, remainedInStock)
VALUES
('IN', NEW.count,_avgCost, _avgCost*NEW.count,_avgCost, 'INVENTORY_TRANSFER', NEW.transferId, NEW.productId, toInv, fromInv, NOW(), latestQuantityInOrigin-NEW.count);
end;
-- ------------------------------------------
-- index: 4
-- Trigger: trg_order_item_after_insert
-- Event: INSERT
-- Table: Order_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_order_item_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_order_item_after_insert` AFTER INSERT ON `Order_Items` FOR EACH ROW BEGIN
UPDATE Stock_Reservations SET quantity = quantity + NEW.quantity
WHERE orderId = NEW.orderId AND productId = NEW.productId;
END;
-- ------------------------------------------
-- index: 5
-- Trigger: trg_order_item_after_update
-- Event: UPDATE
-- Table: Order_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_order_item_after_update`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_order_item_after_update` AFTER UPDATE ON `Order_Items` FOR EACH ROW BEGIN
UPDATE Stock_Reservations
SET quantity = quantity - OLD.quantity + NEW.quantity
WHERE orderId = NEW.orderId AND productId = NEW.productId;
END;
-- ------------------------------------------
-- index: 6
-- Trigger: trg_order_item_after_delete
-- Event: DELETE
-- Table: Order_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_order_item_after_delete`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_order_item_after_delete` AFTER DELETE ON `Order_Items` FOR EACH ROW BEGIN
UPDATE Stock_Reservations SET quantity = quantity - OLD.quantity
WHERE orderId = OLD.orderId AND productId = OLD.productId;
END;
-- ------------------------------------------
-- index: 7
-- Trigger: trg_order_after_cancel
-- Event: UPDATE
-- Table: Orders
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_order_after_cancel`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_order_after_cancel` AFTER UPDATE ON `Orders` FOR EACH ROW BEGIN
IF NEW.status = 'CANCELED' OR NEW.status = 'REJECTED' OR NEW.status = 'DONE' THEN
UPDATE Stock_Reservations sr SET quantity = 0
WHERE sr.orderId = NEW.id;
END IF;
END;
-- ------------------------------------------
-- index: 8
-- Trigger: trg_purchase_receipt_item_after_insert
-- Event: INSERT
-- Table: Purchase_Receipt_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_purchase_receipt_item_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_purchase_receipt_item_after_insert` AFTER INSERT ON `Purchase_Receipt_Items` FOR EACH ROW BEGIN DECLARE latestQuantity DECIMAL(10, 2) DEFAULT 0;
DECLARE invId INT;
DECLARE suppId INT;
-- Get inventory & supplier from
SELECT inventoryId, supplierId
INTO invId, suppId
FROM Purchase_Receipts
WHERE id = NEW.receiptId;
-- Get current stock quantity (if exists)
SELECT COALESCE(quantity, 0)
INTO latestQuantity
FROM Stock_Balance sb
WHERE sb.inventoryId = invId
AND sb.productId = NEW.productId
LIMIT 1;
-- Insert stock movement
INSERT INTO Stock_Movements (
type,
quantity,
unitPrice,
totalCost,
referenceType,
referenceId,
productId,
inventoryId,
avgCost,
supplierId,
remainedInStock,
createdAt
)
VALUES (
'IN',
NEW.count,
NEW.unitPrice,
NEW.totalAmount,
'PURCHASE',
NEW.receiptId,
NEW.productId,
invId,
CASE
WHEN NEW.count = 0 THEN 0
ELSE NEW.totalAmount / NEW.count
END
,
suppId,
latestQuantity + NEW.count,
NOW()
);
END;
-- ------------------------------------------
-- index: 9
-- Trigger: trg_pr_payment_before_insert
-- Event: INSERT
-- Table: Purchase_Receipt_Payments
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_pr_payment_before_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_pr_payment_before_insert` BEFORE INSERT ON `Purchase_Receipt_Payments` FOR EACH ROW BEGIN
DECLARE receiptTotal DECIMAL(14,2);
DECLARE paid DECIMAL(14,2);
SELECT totalAmount, paidAmount
INTO receiptTotal, paid
FROM Purchase_Receipts
WHERE id = NEW.receiptId
FOR UPDATE;
IF NEW.type = 'PAYMENT' AND paid + NEW.amount > receiptTotal THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'مجموع مبلغ پرداختی بیشتر از مبلغ فاکتور است.';
END IF;
END;
-- ------------------------------------------
-- index: 10
-- Trigger: trg_purchase_payment_update_receipt
-- Event: INSERT
-- Table: Purchase_Receipt_Payments
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_purchase_payment_update_receipt`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_purchase_payment_update_receipt` AFTER INSERT ON `Purchase_Receipt_Payments` FOR EACH ROW BEGIN
DECLARE paid DECIMAL(15,2);
DECLARE total DECIMAL(15,2);
SELECT
COALESCE(SUM(
CASE WHEN type = 'PAYMENT' THEN amount ELSE -amount END
),0)
INTO paid
FROM Purchase_Receipt_Payments
WHERE receiptId = NEW.receiptId;
SELECT totalAmount INTO total
FROM Purchase_Receipts
WHERE id = NEW.receiptId;
UPDATE Purchase_Receipts
SET
paidAmount = paid,
status = CASE
WHEN paid = 0 THEN 'UNPAID'
WHEN paid < total THEN 'PARTIALLY_PAID'
ELSE 'PAID'
END
WHERE id = NEW.receiptId;
END;
-- ------------------------------------------
-- index: 11
-- Trigger: trg_purchase_payment_after_insert
-- Event: INSERT
-- Table: Purchase_Receipt_Payments
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_purchase_payment_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_purchase_payment_after_insert` AFTER INSERT ON `Purchase_Receipt_Payments` FOR EACH ROW BEGIN
DECLARE currentBalance DECIMAL(15, 2);
SELECT balance INTO currentBalance
FROM Bank_Account_Balance
WHERE
bankAccountId = NEW.bankAccountId FOR
UPDATE;
IF currentBalance IS NULL THEN SET currentBalance = 0;
INSERT INTO
Bank_Account_Balance (bankAccountId, balance, updatedAt)
VALUES (NEW.bankAccountId, 0, NOW());
END IF;
IF NEW.type = 'PAYMENT' THEN
SET
currentBalance = currentBalance - NEW.amount;
INSERT INTO
Bank_Account_Transactions (
bankAccountId,
type,
amount,
balanceAfter,
referenceType,
referenceId
)
VALUES (
NEW.bankAccountId,
'WITHDRAWAL',
NEW.amount,
currentBalance,
'PURCHASE_PAYMENT',
NEW.id
);
ELSE SET currentBalance = currentBalance + NEW.amount;
INSERT INTO
Bank_Account_Transactions (
bankAccountId,
type,
amount,
balanceAfter,
referenceType,
referenceId
)
VALUES (
NEW.bankAccountId,
'DEPOSIT',
NEW.amount,
currentBalance,
'PURCHASE_REFUND',
NEW.id
);
END IF;
UPDATE Bank_Account_Balance
SET
balance = currentBalance
WHERE
bankAccountId = NEW.bankAccountId;
END;
-- ------------------------------------------
-- index: 12
-- Trigger: trg_pr_payment_after_insert
-- Event: INSERT
-- Table: Purchase_Receipt_Payments
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_pr_payment_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_pr_payment_after_insert` AFTER INSERT ON `Purchase_Receipt_Payments` FOR EACH ROW BEGIN
DECLARE receiptTotal DECIMAL(14,2) Default 0;
DECLARE newPaid DECIMAL(14,2) Default 0;
DECLARE _supplierId INT;
DECLARE lastBalance DECIMAL(14,2) Default 0;
-- Lock receipt row
SELECT COALESCE(totalAmount, 0), COALESCE(paidAmount, 0), supplierId
INTO receiptTotal, newPaid, _supplierId
FROM Purchase_Receipts
WHERE id = NEW.receiptId
FOR UPDATE;
-- Apply payment or refund
IF NEW.type = 'PAYMENT' THEN
SET newPaid = newPaid + NEW.amount;
ELSE
SET newPaid = newPaid - NEW.amount;
END IF;
-- Update receipt
UPDATE Purchase_Receipts
SET
paidAmount = newPaid,
status =
CASE
WHEN newPaid = 0 THEN 'UNPAID'
WHEN newPaid < receiptTotal THEN 'PARTIALLY_PAID'
ELSE 'PAID'
END
WHERE id = NEW.receiptId;
-- Get last supplier balance
SELECT IFNULL(balance, 0)
INTO lastBalance
FROM Supplier_Ledger
WHERE supplierId = _supplierId
ORDER BY id DESC
LIMIT 1;
-- Insert supplier ledger
INSERT INTO Supplier_Ledger
(
supplierId,
debit,
credit,
balance,
sourceType,
sourceId,
createdAt
)
VALUES
(
_supplierId,
IF(NEW.type = 'REFUND', NEW.amount, 0),
IF(NEW.type = 'PAYMENT', NEW.amount, 0),
lastBalance
+ IF(NEW.type = 'PAYMENT', NEW.amount, 0)
- IF(NEW.type = 'REFUND', NEW.amount, 0),
'PAYMENT',
NEW.id,
NOW()
);
END;
-- ------------------------------------------
-- index: 13
-- Trigger: trg_pr_payment_after_delete
-- Event: DELETE
-- Table: Purchase_Receipt_Payments
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_pr_payment_after_delete`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_pr_payment_after_delete` AFTER DELETE ON `Purchase_Receipt_Payments` FOR EACH ROW BEGIN
DECLARE receiptTotal DECIMAL(14,2);
DECLARE newPaid DECIMAL(14,2);
SELECT totalAmount, paidAmount
INTO receiptTotal, newPaid
FROM Purchase_Receipts
WHERE id = OLD.receiptId
FOR UPDATE;
IF OLD.type = 'PAYMENT' THEN
SET newPaid = newPaid - OLD.amount;
ELSE
SET newPaid = newPaid + OLD.amount;
END IF;
UPDATE Purchase_Receipts
SET
paidAmount = newPaid,
status =
CASE
WHEN newPaid = 0 THEN 'UNPAID'
WHEN newPaid < receiptTotal THEN 'PARTIALLY_PAID'
ELSE 'PAID'
END
WHERE id = OLD.receiptId;
END;
-- ------------------------------------------
-- index: 14
-- Trigger: trg_purchase_receipt_after_insert
-- Event: INSERT
-- Table: Purchase_Receipts
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_purchase_receipt_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_purchase_receipt_after_insert` AFTER INSERT ON `Purchase_Receipts` FOR EACH ROW BEGIN
DECLARE lastBalance DECIMAL(14,2) DEFAULT 0;
SELECT COALESCE(balance, 0)
INTO lastBalance
FROM Supplier_Ledger
WHERE supplierId = NEW.supplierId
ORDER BY id DESC
LIMIT 1;
INSERT INTO Supplier_Ledger
(
supplierId,
debit,
credit,
balance,
sourceType,
sourceId,
createdAt
)
VALUES
(
NEW.supplierId,
NEW.totalAmount,
0,
lastBalance - NEW.totalAmount,
'PURCHASE',
NEW.id,
NOW()
);
END;
-- ------------------------------------------
-- index: 15
-- Trigger: trg_sales_invoice_items_before_insert
-- Event: INSERT
-- Table: Sales_Invoice_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_sales_invoice_items_before_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_sales_invoice_items_before_insert` BEFORE INSERT ON `Sales_Invoice_Items` FOR EACH ROW BEGIN
DECLARE current_stock DECIMAL(10, 2);
DECLARE inventory_id INT;
SELECT pa.inventoryId INTO inventory_id
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = NEW.invoiceId;
SELECT COALESCE(quantity, 0) INTO current_stock
FROM Stock_Balance sb
WHERE productId = NEW.productId AND sb.inventoryId = inventory_id
LIMIT 1;
IF NEW.count > current_stock THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'موجودی کالا در انبار کافی نیست.';
END IF;
end;
-- ------------------------------------------
-- index: 16
-- Trigger: trg_sales_invoice_items_after_insert
-- Event: INSERT
-- Table: Sales_Invoice_Items
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_sales_invoice_items_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_sales_invoice_items_after_insert` AFTER INSERT ON `Sales_Invoice_Items` FOR EACH ROW begin DECLARE current_stock DECIMAL(10, 2);
DECLARE inventory_id INT;
DECLARE customer_id INT;
DECLARE pos_id INT;
SELECT posAccountId, customerId INTO pos_id, customer_id
FROM Sales_Invoices si
WHERE si.id = NEW.invoiceId
LIMIT 1;
INSERT INTO Trigger_Logs (name , message) VALUES ('pos_id', pos_id);
INSERT INTO Trigger_Logs (name , message) VALUES ('customer_id', customer_id);
SELECT pa.inventoryId INTO inventory_id
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = NEW.invoiceId;
SELECT COALESCE(quantity, 0) INTO current_stock
FROM Stock_Balance sb
WHERE productId = NEW.productId AND sb.inventoryId = inventory_id
LIMIT 1;
INSERT INTO Stock_Movements (
type,
quantity,
unitPrice,
totalCost,
referenceType,
referenceId,
productId,
inventoryId,
avgCost,
remainedInStock,
customerId,
createdAt
)
VALUES (
'OUT',
NEW.count,
NEW.unitPrice,
NEW.totalAmount,
'SALES',
NEW.invoiceId,
NEW.productId,
inventory_id,
CASE
WHEN NEW.count = 0 THEN 0
ELSE NEW.totalAmount / NEW.count
END,
current_stock - NEW.count,
customer_id,
NOW()
);
END;
-- ------------------------------------------
-- index: 17
-- Trigger: trg_sales_invoice_payment_after_insert
-- Event: INSERT
-- Table: Sales_Invoice_Payments
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_sales_invoice_payment_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_sales_invoice_payment_after_insert` AFTER INSERT ON `Sales_Invoice_Payments` FOR EACH ROW BEGIN
DECLARE currentBalance DECIMAL(15,2);
DECLARE bankAccountId INT;
SELECT pa.bankAccountId INTO bankAccountId
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = NEW.invoiceId;
SELECT balance INTO currentBalance
FROM Bank_Account_Balance
WHERE bankAccountId = bankAccountId
FOR UPDATE;
IF currentBalance IS NULL THEN
SET currentBalance = 0;
INSERT INTO Bank_Account_Balance (bankAccountId, balance)
VALUES (bankAccountId, 0);
END IF;
SET currentBalance = currentBalance + NEW.amount;
INSERT INTO Bank_Account_Transactions
(bankAccountId, type, amount, balanceAfter, referenceType, referenceId)
VALUES
(bankAccountId, 'DEPOSIT', NEW.amount, currentBalance, 'POS_SALE', NEW.id);
UPDATE Bank_Account_Balance
SET balance = currentBalance
WHERE bankAccountId = bankAccountId;
END;
-- ------------------------------------------
-- index: 18
-- Trigger: trg_pos_account_payment_after_insert
-- Event: INSERT
-- Table: Sales_Invoice_Payments
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_pos_account_payment_after_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_pos_account_payment_after_insert` AFTER INSERT ON `Sales_Invoice_Payments` FOR EACH ROW BEGIN
DECLARE _bankAccountId INT;
IF(NEW.paymentMethod != 'CASH') THEN
SELECT cashBankAccountId INTO _bankAccountId
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = NEW.invoiceId;
End IF;
INSERT INTO Bank_Account_Transactions (
bankAccountId,
type,
amount,
balanceAfter,
referenceType,
referenceId
)
VALUES(
_bankAccountId,
'DEPOSIT',
NEW.amount,
0,
'POS_SALE',
NEW.id
);
END;
-- ------------------------------------------
-- index: 19
-- Trigger: trg_stock_transfer
-- Event: INSERT
-- Table: Stock_Movements
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_stock_transfer`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_stock_transfer` AFTER INSERT ON `Stock_Movements` FOR EACH ROW BEGIN IF NEW.referenceType = 'INVENTORY_TRANSFER' THEN IF NEW.type = 'IN' THEN
INSERT INTO
Stock_Balance (
productId,
inventoryId,
quantity,
totalCost,
avgCost,
updatedAt
)
VALUES (
NEW.productId,
NEW.inventoryId,
NEW.quantity,
NEW.totalCost,
CASE
WHEN NEW.quantity = 0 THEN 0
ELSE NEW.totalCost / NEW.quantity
END,
NOW()
)
ON DUPLICATE KEY UPDATE
quantity = quantity + NEW.quantity,
totalCost = totalCost + NEW.totalCost,
avgCost = CASE
WHEN (quantity + NEW.quantity) = 0 THEN 0
ELSE (totalCost + NEW.totalCost) / (quantity + NEW.quantity)
END,
updatedAt = NOW();
END IF;
IF NEW.type = 'OUT' THEN IF EXISTS (
SELECT 1
FROM Stock_Balance sb
WHERE
sb.productId = NEW.productId
AND sb.inventoryId = NEW.inventoryId
) THEN
UPDATE Stock_Balance sb
SET
sb.quantity = sb.quantity - NEW.quantity,
sb.totalCost = sb.totalCost - (sb.avgCost * NEW.quantity),
sb.updatedAt = NOW()
WHERE
sb.productId = NEW.productId
AND sb.inventoryId = NEW.inventoryId;
ELSE
INSERT INTO
Stock_Balance (
productId,
inventoryId,
quantity,
totalCost,
avgCost,
updatedAt
)
VALUES (
NEW.productId,
NEW.inventoryId,
- NEW.quantity,
- COALESCE(NEW.unitPrice, 0) * NEW.quantity,
COALESCE(NEW.unitPrice, 0),
NOW()
);
END IF;
END IF;
END IF;
END;
-- ------------------------------------------
-- index: 20
-- Trigger: trg_stock_purchase_insert
-- Event: INSERT
-- Table: Stock_Movements
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_stock_purchase_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_stock_purchase_insert` AFTER INSERT ON `Stock_Movements` FOR EACH ROW BEGIN IF NEW.referenceType = 'PURCHASE' THEN
INSERT INTO
Stock_Balance (
productId,
quantity,
avgCost,
totalCost,
inventoryId,
updatedAt
)
VALUES (
NEW.productId,
NEW.quantity,
NEW.unitPrice,
NEW.totalCost,
NEW.inventoryId,
NOW()
)
ON DUPLICATE KEY UPDATE
quantity = quantity + NEW.quantity,
totalCost = totalCost + NEW.totalCost,
avgCost = totalCost / quantity;
END IF;
END;
-- ------------------------------------------
-- index: 21
-- Trigger: trg_stock_sale_insert
-- Event: INSERT
-- Table: Stock_Movements
-- ------------------------------------------
DROP TRIGGER IF EXISTS `trg_stock_sale_insert`;
CREATE DEFINER=`pos`@`%` TRIGGER `trg_stock_sale_insert` AFTER INSERT ON `Stock_Movements` FOR EACH ROW BEGIN IF NEW.referenceType = 'SALES' THEN
INSERT INTO
Stock_Balance (
productId,
quantity,
avgCost,
totalCost,
inventoryId,
updatedAt
)
VALUES (
NEW.productId,
NEW.quantity,
NEW.unitPrice,
NEW.totalCost,
NEW.inventoryId,
NOW()
)
ON DUPLICATE KEY UPDATE
quantity = quantity - NEW.quantity,
totalCost = totalCost - NEW.totalCost,
avgCost = totalCost / quantity;
END IF;
END;
-657
View File
@@ -1,657 +0,0 @@
-- Stored Procedures equivalent to triggers
DELIMITER / /
-- Procedure for trg_bank_account_transaction_after_insert
CREATE PROCEDURE update_bank_balance(IN p_bankAccountId INT, IN p_type VARCHAR(20), IN p_amount DECIMAL(15,2))
BEGIN
START TRANSACTION;
IF p_type = 'DEPOSIT' THEN
UPDATE Bank_Account_Balance SET balance = balance + p_amount WHERE bankAccountId = p_bankAccountId;
ELSEIF p_type = 'WITHDRAWAL' THEN
UPDATE Bank_Account_Balance SET balance = balance - p_amount WHERE bankAccountId = p_bankAccountId;
END IF;
COMMIT;
END //
-- Procedure for trg_bank_account_transaction_after_delete
CREATE PROCEDURE update_bank_balance_on_delete(IN p_bankAccountId INT, IN p_amount DECIMAL(15,2))
BEGIN
START TRANSACTION;
UPDATE Bank_Accounts SET balance = balance - p_amount WHERE id = p_bankAccountId;
COMMIT;
END //
-- Procedure for trg_transfer_item_after_insert
CREATE PROCEDURE process_transfer_item(IN p_transferId INT, IN p_productId INT, IN p_count DECIMAL(10,2))
BEGIN
START TRANSACTION;
DECLARE fromInv INT;
DECLARE toInv INT;
DECLARE _avgCost DECIMAL(10,2);
DECLARE latestQuantityInOrigin DECIMAL(10,2);
DECLARE latestQuantityInDestination DECIMAL(10,2);
SELECT fromInventoryId, toInventoryId INTO fromInv, toInv
FROM Inventory_Transfers WHERE id = p_transferId;
SELECT COALESCE(avgCost, 0), COALESCE(quantity, 0) INTO _avgCost, latestQuantityInOrigin FROM Stock_Balance
WHERE ProductId = p_productId AND inventoryId = fromInv LIMIT 1;
SELECT COALESCE(quantity, 0) INTO latestQuantityInDestination FROM Stock_Balance
WHERE ProductId = p_productId AND inventoryId = toInv LIMIT 1;
-- OUT from source
INSERT INTO Stock_Movements
(type, quantity, unitPrice, totalCost, avgCost, referenceType, referenceId, productId, inventoryId, counterInventoryId, createdAt, remainedInStock)
VALUES
('OUT', p_count, _avgCost, _avgCost*p_count, _avgCost, 'INVENTORY_TRANSFER', p_transferId, p_productId, fromInv, toInv, NOW(), latestQuantityInOrigin-p_count);
-- IN to destination
INSERT INTO Stock_Movements
(type, quantity, unitPrice, totalCost, avgCost, referenceType, referenceId, productId, inventoryId, counterInventoryId, createdAt, remainedInStock)
VALUES
('IN', p_count, _avgCost, _avgCost*p_count, _avgCost, 'INVENTORY_TRANSFER', p_transferId, p_productId, toInv, fromInv, NOW(), latestQuantityInOrigin-p_count);
COMMIT;
END //
-- Procedure for trg_order_item_after_insert
CREATE PROCEDURE update_stock_reservation_insert(IN p_orderId INT, IN p_productId INT, IN p_quantity DECIMAL(10,2))
BEGIN
START TRANSACTION;
UPDATE Stock_Reservations SET quantity = quantity + p_quantity
WHERE orderId = p_orderId AND productId = p_productId;
COMMIT;
END //
-- Procedure for trg_order_item_after_update
CREATE PROCEDURE update_stock_reservation_update(IN p_orderId INT, IN p_productId INT, IN p_old_quantity DECIMAL(10,2), IN p_new_quantity DECIMAL(10,2))
BEGIN
START TRANSACTION;
UPDATE Stock_Reservations
SET quantity = quantity - p_old_quantity + p_new_quantity
WHERE orderId = p_orderId AND productId = p_productId;
COMMIT;
END //
-- Procedure for trg_order_item_after_delete
CREATE PROCEDURE update_stock_reservation_delete(IN p_orderId INT, IN p_productId INT, IN p_quantity DECIMAL(10,2))
BEGIN
START TRANSACTION;
UPDATE Stock_Reservations SET quantity = quantity - p_quantity
WHERE orderId = p_orderId AND productId = p_productId;
COMMIT;
END //
-- Procedure for trg_order_after_cancel
CREATE PROCEDURE cancel_order_stock(IN p_orderId INT, IN p_status VARCHAR(20))
BEGIN
START TRANSACTION;
IF p_status = 'CANCELED' OR p_status = 'REJECTED' OR p_status = 'DONE' THEN
UPDATE Stock_Reservations sr SET quantity = 0
WHERE sr.orderId = p_orderId;
END IF;
COMMIT;
END //
-- Procedure for trg_purchase_receipt_item_after_insert
CREATE PROCEDURE process_purchase_item(IN p_receiptId INT, IN p_productId INT, IN p_count DECIMAL(10,2), IN p_unitPrice DECIMAL(15,2), IN p_totalAmount DECIMAL(15,2))
BEGIN
START TRANSACTION;
DECLARE latestQuantity DECIMAL(10, 2) DEFAULT 0;
DECLARE invId INT;
DECLARE suppId INT;
-- Get inventory & supplier from
SELECT inventoryId, supplierId
INTO invId, suppId
FROM Purchase_Receipts
WHERE id = p_receiptId;
-- Get current stock quantity (if exists)
SELECT COALESCE(quantity, 0)
INTO latestQuantity
FROM Stock_Balance sb
WHERE sb.inventoryId = invId
AND sb.productId = p_productId
LIMIT 1;
-- Insert stock movement
INSERT INTO Stock_Movements (
type,
quantity,
unitPrice,
totalCost,
referenceType,
referenceId,
productId,
inventoryId,
avgCost,
supplierId,
remainedInStock,
createdAt
)
VALUES (
'IN',
p_count,
p_unitPrice,
p_totalAmount,
'PURCHASE',
p_receiptId,
p_productId,
invId,
CASE
WHEN p_count = 0 THEN 0
ELSE p_totalAmount / p_count
END,
suppId,
latestQuantity + p_count,
NOW()
);
COMMIT;
END //
-- Procedure for trg_pr_payment_before_insert
CREATE PROCEDURE validate_payment_before_insert(IN p_receiptId INT, IN p_type VARCHAR(20), IN p_amount DECIMAL(15,2))
BEGIN
START TRANSACTION;
DECLARE receiptTotal DECIMAL(14,2);
DECLARE paid DECIMAL(14,2);
SELECT totalAmount, paidAmount
INTO receiptTotal, paid
FROM Purchase_Receipts
WHERE id = p_receiptId
FOR UPDATE;
IF p_type = 'PAYMENT' AND paid + p_amount > receiptTotal THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'مجموع مبلغ پرداختی بیشتر از مبلغ فاکتور است.';
END IF;
COMMIT;
END //
-- Procedure for trg_purchase_payment_update_receipt
CREATE PROCEDURE update_receipt_payment(IN p_receiptId INT)
BEGIN
START TRANSACTION;
DECLARE paid DECIMAL(15,2);
DECLARE total DECIMAL(15,2);
SELECT
COALESCE(SUM(
CASE WHEN type = 'PAYMENT' THEN amount ELSE -amount END
),0)
INTO paid
FROM Purchase_Receipt_Payments
WHERE receiptId = p_receiptId;
SELECT totalAmount INTO total
FROM Purchase_Receipts
WHERE id = p_receiptId;
UPDATE Purchase_Receipts
SET
paidAmount = paid,
status = CASE
WHEN paid = 0 THEN 'UNPAID'
WHEN paid < total THEN 'PARTIALLY_PAID'
ELSE 'PAID'
END
WHERE id = p_receiptId;
COMMIT;
END //
-- Procedure for trg_purchase_payment_after_insert
CREATE PROCEDURE process_purchase_payment(IN p_bankAccountId INT, IN p_type VARCHAR(20), IN p_amount DECIMAL(15,2), IN p_id INT)
BEGIN
START TRANSACTION;
DECLARE currentBalance DECIMAL(15, 2);
SELECT balance INTO currentBalance
FROM Bank_Account_Balance
WHERE bankAccountId = p_bankAccountId FOR UPDATE;
IF currentBalance IS NULL THEN SET currentBalance = 0;
INSERT INTO Bank_Account_Balance (bankAccountId, balance, updatedAt)
VALUES (p_bankAccountId, 0, NOW());
END IF;
IF p_type = 'PAYMENT' THEN
SET currentBalance = currentBalance - p_amount;
INSERT INTO Bank_Account_Transactions (
bankAccountId,
type,
amount,
balanceAfter,
referenceType,
referenceId
)
VALUES (
p_bankAccountId,
'WITHDRAWAL',
p_amount,
currentBalance,
'PURCHASE_PAYMENT',
p_id
);
ELSE
SET currentBalance = currentBalance + p_amount;
INSERT INTO Bank_Account_Transactions (
bankAccountId,
type,
amount,
balanceAfter,
referenceType,
referenceId
)
VALUES (
p_bankAccountId,
'DEPOSIT',
p_amount,
currentBalance,
'PURCHASE_REFUND',
p_id
);
END IF;
UPDATE Bank_Account_Balance
SET balance = currentBalance
WHERE bankAccountId = p_bankAccountId;
COMMIT;
END //
-- Procedure for trg_pr_payment_after_insert
CREATE PROCEDURE update_supplier_ledger(IN p_receiptId INT, IN p_type VARCHAR(20), IN p_amount DECIMAL(15,2), IN p_id INT)
BEGIN
START TRANSACTION;
DECLARE receiptTotal DECIMAL(14,2) DEFAULT 0;
DECLARE newPaid DECIMAL(14,2) DEFAULT 0;
DECLARE _supplierId INT;
DECLARE lastBalance DECIMAL(14,2) DEFAULT 0;
-- Lock receipt row
SELECT COALESCE(totalAmount, 0), COALESCE(paidAmount, 0), supplierId
INTO receiptTotal, newPaid, _supplierId
FROM Purchase_Receipts
WHERE id = p_receiptId
FOR UPDATE;
-- Apply payment or refund
IF p_type = 'PAYMENT' THEN
SET newPaid = newPaid + p_amount;
ELSE
SET newPaid = newPaid - p_amount;
END IF;
-- Update receipt
UPDATE Purchase_Receipts
SET
paidAmount = newPaid,
status =
CASE
WHEN newPaid = 0 THEN 'UNPAID'
WHEN newPaid < receiptTotal THEN 'PARTIALLY_PAID'
ELSE 'PAID'
END
WHERE id = p_receiptId;
-- Get last supplier balance
SELECT IFNULL(balance, 0)
INTO lastBalance
FROM Supplier_Ledger
WHERE supplierId = _supplierId
ORDER BY id DESC
LIMIT 1;
-- Insert supplier ledger
INSERT INTO Supplier_Ledger
(
supplierId,
debit,
credit,
balance,
sourceType,
sourceId,
createdAt
)
VALUES
(
_supplierId,
IF(p_type = 'REFUND', p_amount, 0),
IF(p_type = 'PAYMENT', p_amount, 0),
lastBalance
+ IF(p_type = 'PAYMENT', p_amount, 0)
- IF(p_type = 'REFUND', p_amount, 0),
'PAYMENT',
p_id,
NOW()
);
COMMIT;
END //
-- Procedure for trg_pr_payment_after_delete
CREATE PROCEDURE update_receipt_on_payment_delete(IN p_receiptId INT, IN p_type VARCHAR(20), IN p_amount DECIMAL(15,2))
BEGIN
DECLARE receiptTotal DECIMAL(14,2);
DECLARE newPaid DECIMAL(14,2);
SELECT totalAmount, paidAmount
INTO receiptTotal, newPaid
FROM Purchase_Receipts
WHERE id = p_receiptId
FOR UPDATE;
IF p_type = 'PAYMENT' THEN
SET newPaid = newPaid - p_amount;
ELSE
SET newPaid = newPaid + p_amount;
END IF;
UPDATE Purchase_Receipts
SET
paidAmount = newPaid,
status =
CASE
WHEN newPaid = 0 THEN 'UNPAID'
WHEN newPaid < receiptTotal THEN 'PARTIALLY_PAID'
ELSE 'PAID'
END
WHERE id = p_receiptId;
END //
-- Procedure for trg_purchase_receipt_after_insert
CREATE PROCEDURE insert_supplier_ledger_purchase(IN p_supplierId INT, IN p_totalAmount DECIMAL(15,2), IN p_id INT)
BEGIN
DECLARE lastBalance DECIMAL(14,2) DEFAULT 0;
SELECT COALESCE(balance, 0)
INTO lastBalance
FROM Supplier_Ledger
WHERE supplierId = p_supplierId
ORDER BY id DESC
LIMIT 1;
INSERT INTO Supplier_Ledger
(
supplierId,
debit,
credit,
balance,
sourceType,
sourceId,
createdAt
)
VALUES
(
p_supplierId,
p_totalAmount,
0,
lastBalance - p_totalAmount,
'PURCHASE',
p_id,
NOW()
);
END //
-- Procedure for trg_sales_invoice_items_before_insert
CREATE PROCEDURE validate_stock_before_sale(IN p_invoiceId INT, IN p_productId INT, IN p_count DECIMAL(10,2))
BEGIN
DECLARE current_stock DECIMAL(10, 2);
DECLARE inventory_id INT;
SELECT pa.inventoryId INTO inventory_id
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = p_invoiceId;
SELECT COALESCE(quantity, 0) INTO current_stock
FROM Stock_Balance sb
WHERE productId = p_productId AND sb.inventoryId = inventory_id
LIMIT 1;
IF p_count > current_stock THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'موجودی کالا در انبار کافی نیست.';
END IF;
END //
-- Procedure for trg_sales_invoice_items_after_insert
CREATE PROCEDURE process_sale_item(IN p_invoiceId INT, IN p_productId INT, IN p_count DECIMAL(10,2), IN p_unitPrice DECIMAL(15,2), IN p_totalAmount DECIMAL(15,2))
BEGIN
DECLARE current_stock DECIMAL(10, 2);
DECLARE inventory_id INT;
DECLARE customer_id INT;
DECLARE pos_id INT;
SELECT posAccountId, customerId INTO pos_id, customer_id
FROM Sales_Invoices si
WHERE si.id = p_invoiceId
LIMIT 1;
SELECT pa.inventoryId INTO inventory_id
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = p_invoiceId;
SELECT COALESCE(quantity, 0) INTO current_stock
FROM Stock_Balance sb
WHERE productId = p_productId AND sb.inventoryId = inventory_id
LIMIT 1;
INSERT INTO Stock_Movements (
type,
quantity,
unitPrice,
totalCost,
referenceType,
referenceId,
productId,
inventoryId,
avgCost,
remainedInStock,
customerId,
createdAt
)
VALUES (
'OUT',
p_count,
p_unitPrice,
p_totalAmount,
'SALES',
p_invoiceId,
p_productId,
inventory_id,
CASE
WHEN p_count = 0 THEN 0
ELSE p_totalAmount / p_count
END,
current_stock - p_count,
customer_id,
NOW()
);
END //
-- Procedure for trg_sales_invoice_payment_after_insert
CREATE PROCEDURE process_sale_payment(IN p_invoiceId INT, IN p_amount DECIMAL(15,2), IN p_id INT)
BEGIN
DECLARE currentBalance DECIMAL(15,2);
DECLARE bankAccountId INT;
SELECT pa.bankAccountId INTO bankAccountId
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = p_invoiceId;
SELECT balance INTO currentBalance
FROM Bank_Account_Balance
WHERE bankAccountId = bankAccountId
FOR UPDATE;
IF currentBalance IS NULL THEN
SET currentBalance = 0;
INSERT INTO Bank_Account_Balance (bankAccountId, balance, updatedAt)
VALUES (bankAccountId, 0, NOW());
END IF;
SET currentBalance = currentBalance + p_amount;
INSERT INTO Bank_Account_Transactions
(bankAccountId, type, amount, balanceAfter, referenceType, referenceId)
VALUES
(bankAccountId, 'DEPOSIT', p_amount, currentBalance, 'POS_SALE', p_id);
UPDATE Bank_Account_Balance
SET balance = currentBalance
WHERE bankAccountId = bankAccountId;
END //
-- Procedure for trg_pos_account_payment_after_insert
CREATE PROCEDURE process_pos_payment(IN p_invoiceId INT, IN p_paymentMethod VARCHAR(20), IN p_amount DECIMAL(15,2), IN p_id INT)
BEGIN
DECLARE _bankAccountId INT;
IF(p_paymentMethod != 'CASH') THEN
SELECT cashBankAccountId INTO _bankAccountId
FROM Pos_Accounts pa
INNER JOIN Sales_Invoices si ON pa.id = si.posAccountId
WHERE si.id = p_invoiceId;
END IF;
INSERT INTO Bank_Account_Transactions (
bankAccountId,
type,
amount,
balanceAfter,
referenceType,
referenceId
)
VALUES(
_bankAccountId,
'DEPOSIT',
p_amount,
0,
'POS_SALE',
p_id
);
END //
-- Procedure for trg_stock_transfer
CREATE PROCEDURE update_stock_balance_transfer(IN p_productId INT, IN p_inventoryId INT, IN p_quantity DECIMAL(10,2), IN p_unitPrice DECIMAL(15,2), IN p_totalCost DECIMAL(15,2), IN p_type VARCHAR(10))
BEGIN
IF p_type = 'IN' THEN
INSERT INTO Stock_Balance (
productId,
inventoryId,
quantity,
totalCost,
avgCost,
updatedAt
)
VALUES (
p_productId,
p_inventoryId,
p_quantity,
p_totalCost,
CASE
WHEN p_quantity = 0 THEN 0
ELSE p_totalCost / p_quantity
END,
NOW()
)
ON DUPLICATE KEY UPDATE
quantity = quantity + p_quantity,
totalCost = totalCost + p_totalCost,
avgCost = CASE
WHEN (quantity + p_quantity) = 0 THEN 0
ELSE (totalCost + p_totalCost) / (quantity + p_quantity)
END,
updatedAt = NOW();
END IF;
IF p_type = 'OUT' THEN
IF EXISTS (
SELECT 1
FROM Stock_Balance sb
WHERE sb.productId = p_productId AND sb.inventoryId = p_inventoryId
) THEN
UPDATE Stock_Balance sb
SET
sb.quantity = sb.quantity - p_quantity,
sb.totalCost = sb.totalCost - (sb.avgCost * p_quantity),
sb.updatedAt = NOW()
WHERE
sb.productId = p_productId
AND sb.inventoryId = p_inventoryId;
ELSE
INSERT INTO Stock_Balance (
productId,
inventoryId,
quantity,
totalCost,
avgCost,
updatedAt
)
VALUES (
p_productId,
p_inventoryId,
- p_quantity,
- COALESCE(p_unitPrice, 0) * p_quantity,
COALESCE(p_unitPrice, 0),
NOW()
);
END IF;
END IF;
END //
-- Procedure for trg_stock_purchase_insert
CREATE PROCEDURE update_stock_balance_purchase(IN p_productId INT, IN p_quantity DECIMAL(10,2), IN p_unitPrice DECIMAL(15,2), IN p_totalCost DECIMAL(15,2), IN p_inventoryId INT)
BEGIN
INSERT INTO Stock_Balance (
productId,
quantity,
avgCost,
totalCost,
inventoryId,
updatedAt
)
VALUES (
p_productId,
p_quantity,
p_unitPrice,
p_totalCost,
p_inventoryId,
NOW()
)
ON DUPLICATE KEY UPDATE
quantity = quantity + p_quantity,
totalCost = totalCost + p_totalCost,
avgCost = totalCost / quantity;
END //
-- Procedure for trg_stock_sale_insert
CREATE PROCEDURE update_stock_balance_sale(IN p_productId INT, IN p_quantity DECIMAL(10,2), IN p_unitPrice DECIMAL(15,2), IN p_totalCost DECIMAL(15,2), IN p_inventoryId INT)
BEGIN
INSERT INTO Stock_Balance (
productId,
quantity,
avgCost,
totalCost,
inventoryId,
updatedAt
)
VALUES (
p_productId,
p_quantity,
p_unitPrice,
p_totalCost,
p_inventoryId,
NOW()
)
ON DUPLICATE KEY UPDATE
quantity = quantity - p_quantity,
totalCost = totalCost - p_totalCost,
avgCost = totalCost / quantity;
END //
DELIMITER;
Vendored
+2
View File
@@ -1,3 +1,5 @@
/// <reference types="multer" />
import { AccessTokenPayload, IConsumerPayload, IPosPayload } from '@/common/models' import { AccessTokenPayload, IConsumerPayload, IPosPayload } from '@/common/models'
import { IPartnerPayload } from '@/common/models/partnerPayload.model' import { IPartnerPayload } from '@/common/models/partnerPayload.model'
+55 -56
View File
@@ -1,66 +1,65 @@
#!/usr/bin/env ts-node // #!/usr/bin/env ts-node
import * as fs from 'fs' // import * as fs from 'fs'
import * as path from 'path' // import * as path from 'path'
import { prisma } from '../src/lib/prisma'
function findModules(dir: string): string[] { // function findModules(dir: string): string[] {
const results: string[] = [] // const results: string[] = []
const entries = fs.readdirSync(dir, { withFileTypes: true }) // const entries = fs.readdirSync(dir, { withFileTypes: true })
for (const e of entries) { // for (const e of entries) {
const full = path.join(dir, e.name) // const full = path.join(dir, e.name)
if (e.isDirectory()) { // if (e.isDirectory()) {
results.push(...findModules(full)) // results.push(...findModules(full))
} else if (e.isFile() && e.name.endsWith('.module.ts')) { // } else if (e.isFile() && e.name.endsWith('.module.ts')) {
results.push(full) // results.push(full)
} // }
} // }
return results // return results
} // }
function moduleNameFromFile(filePath: string) { // function moduleNameFromFile(filePath: string) {
const name = path.basename(filePath).replace('.module.ts', '') // const name = path.basename(filePath).replace('.module.ts', '')
return name.replace(/-module$|\.module$/i, '') // return name.replace(/-module$|\.module$/i, '')
} // }
function makePermissionsFor(moduleName: string) { // function makePermissionsFor(moduleName: string) {
const base = moduleName.replace(/\W+/g, '_').toLowerCase() // const base = moduleName.replace(/\W+/g, '_').toLowerCase()
return [`${base}:create`, `${base}:read`, `${base}:update`, `${base}:delete`] // return [`${base}:create`, `${base}:read`, `${base}:update`, `${base}:delete`]
} // }
async function main() { // async function main() {
// const srcDir = path.resolve(__dirname, '..', 'src') // // const srcDir = path.resolve(__dirname, '..', 'src')
// const moduleFiles = findModules(srcDir) // // const moduleFiles = findModules(srcDir)
// const modules = moduleFiles.map(moduleNameFromFile) // // const modules = moduleFiles.map(moduleNameFromFile)
// const permsMap: Record<string, boolean> = {} // // const permsMap: Record<string, boolean> = {}
// for (const m of modules) { // // for (const m of modules) {
// for (const p of makePermissionsFor(m)) permsMap[p] = false // // for (const p of makePermissionsFor(m)) permsMap[p] = false
// } // // }
// // Upsert roles: ensure admin has full permissions, others get entries added // // // Upsert roles: ensure admin has full permissions, others get entries added
// const roles = await prisma.role.findMany() // // const roles = await prisma.role.findMany()
// for (const r of roles) { // // for (const r of roles) {
// const current: Record<string, any> = (r.permissions as any) || {} // // const current: Record<string, any> = (r.permissions as any) || {}
// const merged = { ...permsMap, ...current } // // const merged = { ...permsMap, ...current }
// // If role name is admin (case-insensitive), set all permissions to true // // // If role name is admin (case-insensitive), set all permissions to true
// if (r.name && r.name.toLowerCase() === 'admin') { // // if (r.name && r.name.toLowerCase() === 'admin') {
// for (const key of Object.keys(merged)) merged[key] = true // // for (const key of Object.keys(merged)) merged[key] = true
// } else { // // } else {
// // keep existing truthy values, otherwise false // // // keep existing truthy values, otherwise false
// for (const key of Object.keys(merged)) merged[key] = merged[key] || false // // for (const key of Object.keys(merged)) merged[key] = merged[key] || false
// } // // }
// await prisma.role.update({ where: { id: r.id }, data: { permissions: merged } }) // // await prisma.role.update({ where: { id: r.id }, data: { permissions: merged } })
// console.log( // // console.log(
// `Updated role ${r.name} (id=${r.id}) with ${Object.keys(merged).length} permissions`, // // `Updated role ${r.name} (id=${r.id}) with ${Object.keys(merged).length} permissions`,
// ) // // )
// } // // }
await prisma.$disconnect() // await prisma.$disconnect()
} // }
main().catch(e => { // main().catch(e => {
console.error(e) // console.error(e)
process.exit(1) // process.exit(1)
}) // })
+143
View File
@@ -0,0 +1,143 @@
// import fs from 'node:fs'
// import path from 'node:path'
// type CsvRow = {
// ID?: string
// DescriptionOfID?: string
// Vat?: string
// Type?: string
// }
// function parseCsvLine(line: string): string[] {
// const result: string[] = []
// let current = ''
// let inQuotes = false
// for (let index = 0; index < line.length; index++) {
// const char = line[index]
// if (char === '"') {
// if (inQuotes && line[index + 1] === '"') {
// current += '"'
// index++
// } else {
// inQuotes = !inQuotes
// }
// continue
// }
// if (char === ',' && !inQuotes) {
// result.push(current)
// current = ''
// continue
// }
// current += char
// }
// result.push(current)
// return result.map(value => value.trim())
// }
// function parseCsv(content: string): CsvRow[] {
// const lines = content
// .split(/\r?\n/)
// .map(line => line.trim())
// .filter(Boolean)
// if (!lines.length) return []
// const headers = parseCsvLine(lines[0])
// return lines.slice(1).map(line => {
// const cols = parseCsvLine(line)
// const row: Record<string, string> = {}
// for (let index = 0; index < headers.length; index++) {
// row[headers[index]] = cols[index] || ''
// }
// return row
// })
// }
// function toBooleanFlags(typeValue: string) {
// const normalized = typeValue || ''
// const isPublic = normalized.includes('شناسه عمومی')
// const isImported = normalized.includes('وارداتی')
// const isDomestic = !isImported
// return { isPublic, isDomestic }
// }
// function parseVat(vatValue: string) {
// const parsed = Number(vatValue || '0')
// if (!Number.isFinite(parsed)) return 0
// return parsed
// }
// async function main() {
// const defaultPath =
// '/Users/ahasani/Desktop/product_good_2026-05-01T11-35-57_part_1_28812448-0dd9-411c-99e1-334c28d781fe.csv'
// const csvPath = process.argv[2] || defaultPath
// const absolutePath = path.resolve(csvPath)
// if (!fs.existsSync(absolutePath)) {
// throw new Error(`CSV file not found: ${absolutePath}`)
// }
// const raw = fs.readFileSync(absolutePath, 'utf8').replace(/^\uFEFF/, '')
// const rows = parseCsv(raw)
// let upserted = 0
// let skipped = 0
// const guild = await prisma.guild.findFirst({})
// for (const row of rows) {
// const code = (row.ID || '').trim()
// const name = (row.DescriptionOfID || '').trim()
// const vat = parseVat((row.Vat || '').trim())
// const type = (row.Type || '').trim()
// const { isPublic, isDomestic } = toBooleanFlags(type)
// if (!code || !name) {
// skipped++
// continue
// }
// await prisma.stockKeepingUnits.upsert({
// where: { code },
// create: {
// code,
// name,
// VAT: vat,
// guild: {
// connect: {
// id: guild?.id,
// },
// },
// is_public: isPublic,
// is_domestic: isDomestic,
// },
// update: {
// name,
// VAT: vat,
// guild: {
// connect: {
// id: guild?.id,
// },
// },
// is_public: isPublic,
// is_domestic: isDomestic,
// },
// })
// upserted++
// }
// }
// main()
// .catch(error => {
// console.error(error)
// process.exit(1)
// })
// .finally(async () => {
// await prisma.$disconnect()
// })
+5 -7
View File
@@ -7,27 +7,25 @@ import { AuthModule } from './modules/auth/auth.module'
import { CatalogModule } from './modules/catalog/catalog.module' import { CatalogModule } from './modules/catalog/catalog.module'
import { ConsumerModule } from './modules/consumer/consumer.module' import { ConsumerModule } from './modules/consumer/consumer.module'
import { EnumsModule } from './modules/enums/enums.module' import { EnumsModule } from './modules/enums/enums.module'
import { PublicInvoicesModule } from './modules/invoices/invoices.module'
import { PartnerModule } from './modules/partners/partners.module' import { PartnerModule } from './modules/partners/partners.module'
import { PosModule } from './modules/pos/pos.module' import { PosModule } from './modules/pos/pos.module'
import { SalesInvoiceItemsModule } from './modules/pos/sales-invoices/sales-invoice-items/sales-invoice-items.module'
import { SalesInvoicePaymentsModule } from './modules/pos/sales-invoices/sales-invoice-payments/sales-invoice-payments.module'
import { TriggerLogsModule } from './modules/trigger-logs/trigger-logs.module'
import { UploaderModule } from './modules/uploader/uploader.module' import { UploaderModule } from './modules/uploader/uploader.module'
import { PrismaModule } from './prisma/prisma.module' import { PrismaModule } from './prisma/prisma.module'
import { RedisModule } from './redis/redis.module'
@Module({ @Module({
imports: [ imports: [
PrismaModule, PrismaModule,
RedisModule,
EnumsModule, EnumsModule,
AdminModule, AdminModule,
AuthModule,
CatalogModule, CatalogModule,
ConsumerModule, ConsumerModule,
PosModule, PosModule,
PartnerModule, PartnerModule,
AuthModule, PublicInvoicesModule,
SalesInvoiceItemsModule,
SalesInvoicePaymentsModule,
TriggerLogsModule,
UploaderModule, UploaderModule,
ApplicationModule, ApplicationModule,
], ],
+175 -2
View File
@@ -1,14 +1,187 @@
import {
InvoiceSettlementType,
TspProviderRequestType,
TspProviderResponseStatus,
} from '@/generated/prisma/enums'
export default { export default {
PaymentMethodType: {
TERMINAL: 'ترمینال',
CASH: 'نقدی',
SET_OFF: 'تهاتر',
CARD: 'کارت',
BANK: 'بانکی',
CHECK: 'چک',
OTHER: 'سایر',
},
UnitType: {
COUNT: 'تعداد', COUNT: 'تعداد',
GRAM: 'گرم', GRAM: 'گرم',
KILO_GRAM: 'کیلوگرم', KILOGRAM: 'کیلوگرم',
LITER: 'لیتر', LITER: 'لیتر',
MILLILITER: 'میلی‌لیتر', MILLILITER: 'میلی‌لیتر',
STANDARD: 'استاندارد', METER: 'متر',
HOUR: 'ساعت',
},
GoodPricingModel: {
STANDARD: 'عمومی',
GOLD: 'طلا', GOLD: 'طلا',
},
CustomerType: {
INDIVIDUAL: 'حقیقی',
LEGAL: 'حقوقی',
UNKNOWN: 'نامشخص',
},
POSStatus: {
ACTIVE: 'فعال',
DISABLED: 'غیرفعال',
},
POSRole: {
MANAGER: 'مدیریت',
OPERATOR: 'اپراتور',
},
LicenseType: {
BASIC: 'پایه',
PRO: 'حرفه‌ای',
ENTERPRISE: 'سازمانی',
},
LicenseStatus: {
ACTIVE: 'فعال',
EXPIRED: 'منقضی',
SUSPENDED: 'غیرفعال',
},
POSType: {
PSP: 'PSP',
MOBILE: 'موبایل',
WEB: 'وب',
API: 'API',
},
UserType: {
LEGAL: 'حقوقی',
INDIVIDUAL: 'حقیقی',
},
AccountType: {
ADMIN: 'مدیر سیستم',
PROVIDER: 'ارائه‌دهنده خدمات',
PARTNER: 'شریک تجاری',
CONSUMER: 'مصرف‌کننده',
},
UserStatus: {
ACTIVE: 'فعال',
INACTIVE: 'غیرفعال',
},
AccountRole: {
OWNER: 'مدیر اصلی',
OPERATOR: 'اپراتور',
ACCOUNTANT: 'حسابدار',
},
AccountStatus: {
ACTIVE: 'فعال', ACTIVE: 'فعال',
SUSPENDED: 'غیرفعال', SUSPENDED: 'غیرفعال',
},
PartnerRole: {
OWNER: 'مدیر اصلی', OWNER: 'مدیر اصلی',
MANAGER: 'مدیریت', MANAGER: 'مدیریت',
OPERATOR: 'اپراتور', OPERATOR: 'اپراتور',
},
PartnerStatus: {
ACTIVE: 'فعال',
SUSPENDED: 'غیرفعال',
},
ProviderRole: {
OWNER: 'مدیر اصلی',
MANAGER: 'مدیریت',
OPERATOR: 'اپراتور',
},
ProviderStatus: {
ACTIVE: 'فعال',
SUSPENDED: 'غیرفعال',
},
BusinessRole: {
MANAGER: 'مدیریت',
OPERATOR: 'اپراتور',
},
ComplexRole: {
MANAGER: 'مدیریت',
OPERATOR: 'اپراتور',
},
ConsumerRole: {
OWNER: 'مدیر اصلی',
MANAGER: 'مدیریت',
OPERATOR: 'اپراتور',
},
ConsumerStatus: {
ACTIVE: 'فعال',
SUSPENDED: 'غیرفعال',
},
ConsumerType: {
INDIVIDUAL: 'حقیقی',
LEGAL: 'حقوقی',
},
TokenType: {
ACCESS: 'دسترسی',
REFRESH: 'نوسازی',
},
ApplicationPlatform: {
ANDROID: 'اندروید',
IOS: 'iOS',
},
ApplicationReleaseType: {
STABLE: 'پایدار',
BETA: 'بتا',
ALPHA: 'آلفا',
},
ApplicationPublisher: {
DIRECT: 'مستقیم',
CAFE_BAZAR: 'کافه بازار',
MAYKET: 'مایکت',
},
TspProviderType: {
NAMA: 'نما',
SUN: 'سان',
},
TspProviderResponseStatus: {
[TspProviderResponseStatus.SUCCESS]: 'تایید شده',
[TspProviderResponseStatus.FAILURE]: 'ناموفق',
[TspProviderResponseStatus.NOT_SEND]: 'ارسال نشده',
[TspProviderResponseStatus.QUEUED]: 'در صف ارسال',
[TspProviderResponseStatus.FISCAL_QUEUED]: 'در انتظار تایید سازمان',
[TspProviderResponseStatus.SEND_FAILURE]: 'خطا در ارسال',
},
InvoiceSettlementType: {
[InvoiceSettlementType.CASH]: 'نقدی',
[InvoiceSettlementType.CREDIT]: 'نسیه',
[InvoiceSettlementType.MIXED]: 'نقدی / نسیه',
},
TspProviderRequestType: {
[TspProviderRequestType.ORIGINAL]: 'اصلی',
[TspProviderRequestType.CORRECTION]: 'اصلاح',
[TspProviderRequestType.REVOKE]: 'ابطال',
[TspProviderRequestType.RETURN]: 'برگشت از فروش',
},
TspProviderCustomerType: {
Unknown: 'ناشناس',
Known: 'شناسایی شده',
},
SKUGuildType: {
GOLD: 'طلا',
},
GoldKarat: {
KARAT_18: '۱۸ عیار',
KARAT_21: '۲۱ عیار',
KARAT_24: '۲۴ عیار',
},
InvoiceTemplateType: {
SALE: 'فروش',
FX_SALE: 'فروش ارزی',
GOLD_JEWELRY: 'طلا و جواهر',
CONTRACT: 'پیمانکاری',
UTILITY: 'قبوض خدماتی',
AIR_TICKET: 'بلیط هواپیما',
EXPORT: 'صادرات',
BILL_OF_LADING: 'بارنامه',
PETROCHEMICAL: 'پتروشیمی',
COMMODITY_EXCHANGE: 'بورس کالا',
INSURANCE: 'بیمه',
},
} }
+28 -19
View File
@@ -1,25 +1,34 @@
export enum TokenType {
ACCESS = 'ACCESS',
REFRESH = 'REFRESH',
}
export enum AccountType {
PARTNER = 'PARTNER',
BUSINESS = 'BUSINESS',
ADMIN = 'ADMIN',
PROVIDER = 'PROVIDER',
POS = 'POS',
}
export enum GoldKarat { export enum GoldKarat {
KARAT_18 = '18', KARAT_18 = 'KARAT_18',
KARAT_21 = '21', KARAT_21 = 'KARAT_21',
KARAT_24 = '24', KARAT_24 = 'KARAT_24',
} }
export enum TspProviderRequestType {
ORIGINAL = 'ORIGINAL',
CORRECTION = 'CORRECTION',
REVOKE = 'REVOKE',
REMOVE = 'REMOVE',
}
export const SKUGuildType = {
GOLD: 'GOLD',
} as const
export type SKUGuildType = (typeof SKUGuildType)[keyof typeof SKUGuildType]
export const UploadedFileTypes = { export const UploadedFileTypes = {
GOOD: 'good', GOOD: 'GOOD',
SERVICE: 'service', SERVICE: 'SERVICE',
PROFILE_AVATAR: 'profile_avatar', PROFILE_AVATAR: 'PROFILE_AVATAR',
PARTNER_LOGO: 'PARTNER_LOGO',
} }
export type UploadedFileTypes = (typeof UploadedFileTypes)[keyof typeof UploadedFileTypes] export type UploadedFileTypes = (typeof UploadedFileTypes)[keyof typeof UploadedFileTypes]
export const TspProviderCustomerType = {
UNKNOWN: 'UNKNOWN',
KNOWN: 'KNOWN',
}
export type TspProviderCustomerType =
(typeof TspProviderCustomerType)[keyof typeof TspProviderCustomerType]
+2 -2
View File
@@ -27,14 +27,14 @@ export class ConsumerGuard {
const consumer = await this.prisma.consumer.findFirst({ const consumer = await this.prisma.consumer.findFirst({
where: { where: {
consumer_accounts: { accounts: {
some: { some: {
id: tokenPayload.account_id, id: tokenPayload.account_id,
}, },
}, },
}, },
select: { select: {
consumer_accounts: { accounts: {
select: { select: {
role: true, role: true,
}, },
+2 -2
View File
@@ -23,14 +23,14 @@ export class PartnerGuard {
const partner = await this.prisma.partner.findFirst({ const partner = await this.prisma.partner.findFirst({
where: { where: {
partner_accounts: { accounts: {
some: { some: {
id: tokenPayload.account_id, id: tokenPayload.account_id,
}, },
}, },
}, },
select: { select: {
partner_accounts: { accounts: {
select: { select: {
role: true, role: true,
}, },
+2 -97
View File
@@ -3,6 +3,7 @@ import { ITokenPayload } from '@/modules/auth/auth.utils'
import { PrismaService } from '@/prisma/prisma.service' import { PrismaService } from '@/prisma/prisma.service'
import { ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common' import { ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common'
import { Request as ExpressRequest } from 'express' import { Request as ExpressRequest } from 'express'
import { QUERY_CONSTANTS } from '../queryConstants'
@Injectable() @Injectable()
export class PosGuard { export class PosGuard {
@@ -18,7 +19,6 @@ export class PosGuard {
throw new ForbiddenException('شما دسترسی لازم را ندارید.') throw new ForbiddenException('شما دسترسی لازم را ندارید.')
} }
const now = new Date()
const cookie = req.cookies const cookie = req.cookies
const { posId } = cookie const { posId } = cookie
@@ -40,22 +40,7 @@ export class PosGuard {
}, },
], ],
license_activation: { license_activation: {
OR: [ ...QUERY_CONSTANTS.LICENSE_ACTIVATION.activeOnDate(),
{
expires_at: {
gte: now,
},
},
{
license_renews: {
some: {
expires_at: {
gte: now,
},
},
},
},
],
}, },
}, },
}) })
@@ -67,85 +52,5 @@ export class PosGuard {
} }
return true return true
// const pos = await this.prisma.pos.findUnique({
// where: {
// id: posId,
// account_id: tokenPayload.account_id,
// },
// select: {
// complex: {
// select: {
// id: true,
// business_activity: {
// select: {
// id: true,
// license_activation: {
// select: {
// expires_at: true,
// },
// },
// },
// },
// },
// },
// },
// })
// if (!pos) {
// throw new ForbiddenException('شما دسترسی لازم را ندارید.')
// }
// if (req.method !== 'GET') {
// if (!pos.complex.business_activity.license_activation) {
// throw new ForbiddenException('برای کاربر شما لایسنس ایجاد نشده است.')
// }
// if (
// pos.complex.business_activity.license_activation.expires_at &&
// new Date().getTime() >
// new Date(pos.complex.business_activity.license_activation.expires_at).getTime()
// ) {
// throw new ForbiddenException('لایسنس شما منقضی شده است.')
// }
// }
// const foundedAccount = await this.prisma.consumerAccount.findUnique({
// where: {
// id: tokenPayload.account_id,
// },
// select: {
// role: true,
// },
// })
// if (foundedAccount?.role === 'OWNER') {
// return true
// }
// const accountPermissions = await this.prisma.permissionConsumer.findUnique({
// where: {
// consumer_account_id: tokenPayload.account_id,
// },
// select: {
// pos_permissions: true,
// business_permissions: true,
// complex_permissions: true,
// },
// })
// if (accountPermissions?.pos_permissions.some(p => p.pos_id === posId)) {
// return true
// }
// if (
// accountPermissions?.complex_permissions.some(p => p.complex_id === pos.complex.id)
// ) {
// return true
// }
// if (
// accountPermissions?.business_permissions.some(
// p => p.business_id === pos.complex.business_activity.id,
// )
// ) {
// return true
// }
} }
} }
@@ -0,0 +1,63 @@
export interface FetchRequestContext {
url: string
init: RequestInit
}
export interface FetchRequestInterceptor {
onRequest?(
context: FetchRequestContext,
): Promise<FetchRequestContext> | FetchRequestContext
onResponse?(
context: FetchRequestContext,
response: Response,
): Promise<Response> | Response
onError?(context: FetchRequestContext, error: unknown): Promise<void> | void
}
function mergeHeaders(
currentHeaders: HeadersInit | undefined,
newHeaders: HeadersInit,
): Headers {
const mergedHeaders = new Headers(currentHeaders)
const incomingHeaders = new Headers(newHeaders)
incomingHeaders.forEach((value, key) => {
mergedHeaders.set(key, value)
})
return mergedHeaders
}
export function createHeaderRequestInterceptor(
headers: HeadersInit,
): FetchRequestInterceptor {
return {
onRequest(context) {
return {
...context,
init: {
...context.init,
headers: mergeHeaders(context.init.headers, headers),
},
}
},
}
}
export function createEnsureSuccessResponseInterceptor(): FetchRequestInterceptor {
return {
async onResponse(_, response) {
if (response.ok) {
return response
}
const errorBody = await response.text().catch(() => '')
const normalizedBody = errorBody
return Promise.reject({
status: response.status,
statusText: response.statusText,
body: normalizedBody,
})
},
}
}
@@ -85,7 +85,6 @@ export class ResponseMappingInterceptor implements NestInterceptor {
break break
case 'paginate': { case 'paginate': {
const { items: a, ...rest } = wrapped const { items: a, ...rest } = wrapped
console.log(rest)
const items = Array.isArray(wrapped.items) ? wrapped.items : [] const items = Array.isArray(wrapped.items) ? wrapped.items : []
const total = const total =
@@ -96,7 +95,7 @@ export class ResponseMappingInterceptor implements NestInterceptor {
meta = { meta = {
totalRecords: total, totalRecords: total,
totalPages: Math.max(1, Math.ceil(total / perPage)), totalPages: Math.max(1, Math.ceil(total / perPage)),
page, page: parseInt(page),
perPage, perPage,
} }
break break
@@ -189,7 +188,7 @@ export class ResponseMappingInterceptor implements NestInterceptor {
meta = { meta = {
totalRecords: total, totalRecords: total,
totalPages: Math.max(1, Math.ceil(total / perPage)), totalPages: Math.max(1, Math.ceil(total / perPage)),
page, page: parseInt(page),
perPage, perPage,
} }
} }
@@ -4,6 +4,7 @@ export interface SaleInvoiceGoldTypePayload {
karat: keyof typeof GoldKarat karat: keyof typeof GoldKarat
wages: number wages: number
profit: number profit: number
commission: number
} }
export interface SaleInvoiceStandardPayload {} export interface SaleInvoiceStandardPayload {}
@@ -0,0 +1,62 @@
import { BusinessActivitySelect } from '@/generated/prisma/models'
export const summarySelect: BusinessActivitySelect = {
id: true,
name: true,
economic_code: true,
created_at: true,
fiscal_id: true,
invoice_number_sequence: true,
partner_token: true,
guild: {
select: {
id: true,
code: true,
name: true,
},
},
license_activation: {
select: {
id: true,
starts_at: true,
expires_at: true,
license: {
select: {
activation: {
select: {
account_allocations: {
select: {
id: true,
account_id: true,
},
},
},
},
},
},
},
},
}
export const select: BusinessActivitySelect = {
...summarySelect,
}
export const mappedData = (businessActivity: any) => {
const { license_activation, ...rest } = businessActivity
const { license, ...license_activation_rest } = license_activation
const { account_allocations } = license.activation
console.log('license_activation', license_activation)
return {
...rest,
license_info: {
...license_activation_rest,
accounts_limit: account_allocations.length,
allocated_account_count: account_allocations.filter(
allocation => allocation.account_id,
).length,
},
}
}
+83
View File
@@ -0,0 +1,83 @@
import { ConsumerSelect, ConsumerWhereInput } from '@/generated/prisma/models'
const now = new Date()
export const infoSelect: ConsumerSelect = {
type: true,
legal: {
select: {
name: true,
registration_code: true,
partner: {
select: {
id: true,
name: true,
code: true,
},
},
},
},
individual: {
select: {
first_name: true,
last_name: true,
mobile_number: true,
national_code: true,
partner: {
select: {
id: true,
name: true,
code: true,
},
},
},
},
}
export const activeBusinessCount: ConsumerSelect = {
_count: {
select: {
business_activities: {
where: {
license_activation: {
OR: [
{
expires_at: {
gt: now,
},
},
{
license_renews: {
some: {
expires_at: {
gt: now,
},
},
},
},
],
},
},
},
},
},
}
export const consumerRelatedPartner = (partner_id: string): ConsumerWhereInput => ({
OR: [
{
legal: {
is: {
partner_id,
},
},
},
{
individual: {
is: {
partner_id,
},
},
},
],
})
+10 -2
View File
@@ -1,5 +1,13 @@
import * as pos from './pos' import * as BUSINESS_ACTIVITIES from './businessActivities'
import * as CONSUMER from './consumer'
import * as LICENSE_ACTIVATION from './licenseActivation'
import * as POS from './pos'
import * as SALE_INVOICE from './saleInvoice'
export const QUERY_CONSTANTS = { export const QUERY_CONSTANTS = {
pos, POS,
CONSUMER,
LICENSE_ACTIVATION,
SALE_INVOICE,
BUSINESS_ACTIVITIES,
} }
@@ -0,0 +1,18 @@
export const activeOnDate = (expires_at: Date = new Date()) => ({
OR: [
{
expires_at: {
gte: expires_at,
},
},
{
license_renews: {
some: {
expires_at: {
gte: expires_at,
},
},
},
},
],
})
+134
View File
@@ -0,0 +1,134 @@
import { SalesInvoiceSelect } from '@/generated/prisma/models'
export const summarySelect: SalesInvoiceSelect = {
id: true,
code: true,
invoice_date: true,
invoice_number: true,
main_id: true,
total_amount: true,
tax_id: true,
type: true,
notes: true,
created_at: true,
settlement_type: true,
unknown_customer: true,
last_attempt_no: true,
last_tsp_status: true,
customer: {
select: {
type: true,
individual: {
select: {
first_name: true,
last_name: true,
mobile_number: true,
national_id: true,
economic_code: true,
postal_code: true,
},
},
legal: {
select: {
name: true,
economic_code: true,
registration_number: true,
postal_code: true,
},
},
},
},
reference_invoice: {
select: {
id: true,
invoice_number: true,
},
},
referenced_by: {
select: {
id: true,
invoice_number: true,
},
},
}
export const select: SalesInvoiceSelect = {
...summarySelect,
discount_amount: true,
tax_amount: true,
updated_at: true,
unknown_customer: true,
pos: {
select: {
id: true,
name: true,
complex: {
select: {
id: true,
name: true,
business_activity: {
select: {
id: true,
name: true,
economic_code: true,
guild: {
select: {
name: true,
},
},
},
},
},
},
},
},
consumer_account: {
select: {
id: true,
role: true,
account: {
select: {
username: true,
},
},
},
},
items: {
select: {
id: true,
good_id: true,
service_id: true,
quantity: true,
measure_unit_code: true,
measure_unit_text: true,
sku_code: true,
unit_price: true,
discount_amount: true,
tax_amount: true,
total_amount: true,
notes: true,
payload: true,
good_snapshot: true,
},
},
payments: {
select: {
id: true,
amount: true,
payment_method: true,
paid_at: true,
created_at: true,
terminal_info: {
select: {
terminal_id: true,
stan: true,
rrn: true,
transaction_date_time: true,
customer_card_no: true,
description: true,
},
},
},
},
}
@@ -0,0 +1,42 @@
import { QUERY_CONSTANTS } from '@/common/queryConstants'
import { BusinessActivitySelect } from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { Injectable } from '@nestjs/common'
@Injectable()
export class BusinessActivitiesQueryService {
constructor(private readonly prisma: PrismaService) {}
readonly baseSelect: BusinessActivitySelect = {
...QUERY_CONSTANTS.BUSINESS_ACTIVITIES.summarySelect,
}
async findAllByConsumer(consumer_id: string, select: BusinessActivitySelect) {
const businessActivities = await this.prisma.businessActivity.findMany({
where: { consumer_id },
select: this.baseSelect,
})
return businessActivities.map(QUERY_CONSTANTS.BUSINESS_ACTIVITIES.mappedData)
}
async findOneByConsumer(
consumer_id: string,
id: string,
select: BusinessActivitySelect,
orThrow = false,
) {
// if (orThrow) {
// return await this.prisma.businessActivity.findUniqueOrThrow({
// where: { consumer_id, id },
// select,
// })
// }
const businessActivity = await this.prisma.businessActivity.findUnique({
where: { consumer_id, id },
select: QUERY_CONSTANTS.BUSINESS_ACTIVITIES.select,
})
return QUERY_CONSTANTS.BUSINESS_ACTIVITIES.mappedData(businessActivity)
}
}
+124
View File
@@ -0,0 +1,124 @@
import { UploadedFileTypes } from '@/common/enums/enums'
import { GoodPricingModel } from '@/generated/prisma/enums'
import { GoodSelect } from '@/generated/prisma/models'
import { UploaderService } from '@/modules/uploader/uploader.service'
import { Injectable } from '@nestjs/common'
export interface CreateGoodDto {
name: string
sku_id: string
category_id: string
measure_unit_id: string
pricing_model: GoodPricingModel
description?: string
is_default_guild_good?: boolean
}
export interface UpdateGoodDto extends Partial<CreateGoodDto> {}
@Injectable()
export class GoodsSharedService {
constructor(private readonly uploaderService: UploaderService) {}
readonly defaultSelect: GoodSelect = {
id: true,
name: true,
pricing_model: true,
image_url: true,
description: true,
sku: {
select: {
id: true,
name: true,
},
},
measure_unit: {
select: {
id: true,
name: true,
},
},
category: {
select: {
id: true,
name: true,
},
},
}
async prepareCreateData(data: CreateGoodDto, file?: Express.Multer.File) {
const { category_id, measure_unit_id, sku_id, ...rest } = data
let image_url = ''
if (file) {
const uploadedUrl = await this.uploaderService.uploadFile(
file,
UploadedFileTypes.GOOD,
)
image_url = uploadedUrl || ''
}
return {
...rest,
image_url,
sku: {
connect: {
id: sku_id,
},
},
measure_unit: {
connect: {
id: measure_unit_id,
},
},
category: {
connect: {
id: category_id,
},
},
}
}
async prepareUpdateData(id: string, data: UpdateGoodDto, file?: Express.Multer.File) {
const { category_id, measure_unit_id, sku_id, ...rest } = data
let image_url = ''
if (file) {
const uploadedUrl = await this.uploaderService.uploadFile(
file,
UploadedFileTypes.GOOD,
)
image_url = uploadedUrl || ''
}
const updateData: any = {
...rest,
image_url,
}
if (sku_id) {
updateData.sku = { connect: { id: sku_id } }
}
if (measure_unit_id) {
updateData.measure_unit = { connect: { id: measure_unit_id } }
}
if (category_id) {
updateData.category = { connect: { id: category_id } }
}
return updateData
}
async handleImageUpdate(id: string, prisma: any, image_url: string) {
if (image_url) {
const prevImage = await prisma.good.findUnique({
where: { id },
select: { image_url: true },
})
if (prevImage?.image_url) {
this.uploaderService.deleteFile(prevImage.image_url, UploadedFileTypes.GOOD)
}
}
}
}
@@ -0,0 +1,29 @@
import { PrismaService } from '@/prisma/prisma.service'
import { BadRequestException, Injectable } from '@nestjs/common'
import { ConsumerRole } from 'generated/prisma/enums'
@Injectable()
export class SharedSaleInvoiceAccessService {
constructor(private readonly prisma: PrismaService) {}
async getConsumerIdWithPosAccess(
consumerAccountId: string,
posId: string,
): Promise<string> {
const consumer = await this.prisma.consumerAccount.findUnique({
where: {
id: consumerAccountId,
OR: [{ pos: { id: posId } }, { role: ConsumerRole.OWNER }],
},
select: {
consumer_id: true,
},
})
if (!consumer) {
throw new BadRequestException('شما دسترسی لازم برای ارسال صورت‌حساب را ندارید.')
}
return consumer.consumer_id
}
}
@@ -0,0 +1,547 @@
import { SharedSaleInvoiceAccessService } from '@/common/services/saleInvoices/sale-invoice-access.service'
import {
PosCorrectionSalesInvoiceDto,
PosReturnSalesInvoiceDto,
} from '@/modules/pos/sales-invoices/dto/create-sales-invoice.dto'
import { SalesInvoiceTspService } from '@/modules/tspProviders/sales-invoice-tsp.service'
import { PrismaService } from '@/prisma/prisma.service'
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'
import {
CustomerType,
TspProviderRequestType,
TspProviderResponseStatus,
} from 'generated/prisma/enums'
import { SharedSaleInvoiceCreateService } from './sale-invoice-create.service'
@Injectable()
export class SharedSaleInvoiceActionsService {
constructor(
private prisma: PrismaService,
private readonly salesInvoiceTspService: SalesInvoiceTspService,
private readonly saleInvoiceAccessService: SharedSaleInvoiceAccessService,
private sharedSaleInvoiceCreateService: SharedSaleInvoiceCreateService,
) {}
async send(consumerAccountId: string, posId: string, invoiceId: string) {
await this.saleInvoiceAccessService.getConsumerIdWithPosAccess(
consumerAccountId,
posId,
)
return this.salesInvoiceTspService.originalSend(posId, invoiceId)
}
async retry(consumerAccountId: string, posId: string, invoiceId: string) {
await this.saleInvoiceAccessService.getConsumerIdWithPosAccess(
consumerAccountId,
posId,
)
return this.salesInvoiceTspService.originalSend(posId, invoiceId)
}
async revoke(
consumerAccountId: string,
posId: string,
complexId: string,
businessId: string,
invoiceId: string,
) {
await this.saleInvoiceAccessService.getConsumerIdWithPosAccess(
consumerAccountId,
posId,
)
return this.salesInvoiceTspService.revoke(
consumerAccountId,
posId,
complexId,
businessId,
invoiceId,
)
}
async correction(
data: PosCorrectionSalesInvoiceDto,
consumerAccountId: string,
posId: string,
complexId: string,
businessId: string,
invoiceId: string,
) {
await this.saleInvoiceAccessService.getConsumerIdWithPosAccess(
consumerAccountId,
posId,
)
const newInvoice = await this.prisma.$transaction(async tx => {
const relatedInvoice = await tx.salesInvoice.findUnique({
where: {
id: invoiceId,
pos_id: posId,
},
select: {
id: true,
invoice_date: true,
settlement_type: true,
customer_id: true,
main_id: true,
last_tsp_status: true,
referenced_by: {
select: {
id: true,
},
},
customer: {
select: {
type: true,
},
},
total_amount: true,
items: {
select: {
id: true,
good_id: true,
service_id: true,
quantity: true,
unit_price: true,
total_amount: true,
discount_amount: true,
tax_amount: true,
payload: true,
notes: true,
},
},
},
})
if (!relatedInvoice) {
throw new NotFoundException('صورت‌حساب مورد نظر شما یافت نشد.')
}
if (relatedInvoice.last_tsp_status !== TspProviderResponseStatus.SUCCESS) {
throw new BadRequestException('امکان اصلاح این صورت‌حساب وجود ندارد.')
}
if (relatedInvoice.referenced_by) {
throw new BadRequestException(
'این صورت‌حساب قبلا با یک صورت‌حساب دیگر جایگزین شده است.',
)
}
const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date)
const originalInvoiceDate = this.normalizeInvoiceDate(relatedInvoice.invoice_date)
const originalItemsByKey = new Map(
relatedInvoice.items.map(item => [this.getItemKey(item), item]),
)
const usedItemKeys = new Set<string>()
const normalizedItems = data.items.map(item => {
const itemKey = this.getItemKey(item)
if (!itemKey) {
throw new BadRequestException('هر قلم باید شناسه کالا یا خدمت داشته باشد.')
}
usedItemKeys.add(itemKey)
const originalItem = originalItemsByKey.get(itemKey)
if (!originalItem) {
throw new BadRequestException(
'فقط اقلام موجود در صورت‌حساب اصلی قابل اصلاح هستند.',
)
}
const originalQuantity = Number(originalItem.quantity)
const requestedQuantity = Number(item.quantity)
const originalUnitPrice = Number(originalItem.unit_price)
const requestedUnitPrice = Number(item.unit_price)
const originalTotalAmount = this.roundAmount(Number(originalItem.total_amount))
const requestedTotalAmount = this.roundAmount(Number(item.total_amount))
if (requestedQuantity <= 0) {
throw new BadRequestException(
'تعداد/مقدار اقلام اصلاحی باید بیشتر از صفر باشد.',
)
}
// if (requestedUnitPrice < originalUnitPrice) {
// throw new BadRequestException(
// 'مبلغ واحد اقلام اصلاحی نمی‌تواند کمتر از صورت‌حساب اصلی باشد.',
// )
// }
return {
invoice_id: originalItem.id,
good_id: originalItem.good_id,
service_id: originalItem.service_id || undefined,
quantity: requestedQuantity,
unit_price: requestedUnitPrice,
total_amount: requestedTotalAmount,
discount_amount: this.roundAmount(Number(item.discount_amount || 0)),
tax_amount: this.roundAmount(Number(item.tax_amount || 0)),
payload: item.payload ? JSON.parse(JSON.stringify(item.payload)) : undefined,
notes: item.notes || undefined,
}
})
if (usedItemKeys.size !== relatedInvoice.items.length) {
throw new BadRequestException(
'فقط اقلام موجود در صورت‌حساب اصلی قابل اصلاح هستند.',
)
}
const hasChangedQuantity = normalizedItems.some(item => {
const originalItem = relatedInvoice.items.find(
relatedItem => this.getItemKey(relatedItem) === this.getItemKey(item),
)
if (!originalItem) {
return false
}
return Number(originalItem.quantity) !== Number(item.quantity)
})
const hasChangedAmount = normalizedItems.some(item => {
const originalItem = relatedInvoice.items.find(
relatedItem => this.getItemKey(relatedItem) === this.getItemKey(item),
)
if (!originalItem) {
return false
}
return (
this.roundAmount(Number(originalItem.total_amount)) !==
this.roundAmount(Number(item.total_amount))
)
})
const hasChangedInvoiceDate = normalizedInvoiceDate !== originalInvoiceDate
if (!hasChangedQuantity && !hasChangedAmount && !hasChangedInvoiceDate) {
throw new BadRequestException('مقداری تغییر نکرده است.')
}
const requestedTotalAmount = this.roundAmount(Number(data.total_amount))
const originalTotalAmount = this.roundAmount(Number(relatedInvoice.total_amount))
const totalDiff = this.roundAmount(requestedTotalAmount - originalTotalAmount)
const paymentsAmount = this.roundAmount(this.getPaymentsAmount(data.payments))
if (totalDiff > 0 && paymentsAmount !== totalDiff) {
throw new BadRequestException(
'جمع پرداختی باید برابر با اختلاف مبلغ صورت‌حساب اصلاحی و صورت‌حساب مرجع باشد.',
)
}
if (totalDiff === 0 && paymentsAmount !== 0) {
throw new BadRequestException('در صورت عدم افزایش مبلغ، پرداختی نباید ثبت شود.')
}
const newInvoice = await this.sharedSaleInvoiceCreateService.create({
tx,
data: {
invoice_date: new Date(normalizedInvoiceDate),
items: normalizedItems as any,
total_amount: requestedTotalAmount,
discount_amount: this.roundAmount(Number(data.discount_amount)),
tax_amount: this.roundAmount(Number(data.tax_amount)),
customer_id: relatedInvoice.customer_id || undefined,
customer_type: relatedInvoice.customer?.type || CustomerType.UNKNOWN,
settlement_type: relatedInvoice.settlement_type,
payments: data.payments,
} as any,
businessId,
complexId,
posId,
consumerAccountId,
main_invoice_id: relatedInvoice.main_id || relatedInvoice.id,
ref_invoice_id: relatedInvoice.id,
type: TspProviderRequestType.CORRECTION,
})
await tx.salesInvoice.update({
where: {
id: relatedInvoice.id,
},
data: {
referenced_by: {
connect: {
id: newInvoice.id,
},
},
},
})
return newInvoice
})
return this.salesInvoiceTspService.correctionSend(posId, newInvoice.id)
}
async return(
data: PosReturnSalesInvoiceDto,
consumerAccountId: string,
pos_id: string,
complex_id: string,
business_id: string,
invoiceId: string,
) {
await this.saleInvoiceAccessService.getConsumerIdWithPosAccess(
consumerAccountId,
pos_id,
)
const newInvoice = await this.prisma.$transaction(async tx => {
const relatedInvoice = await tx.salesInvoice.findUnique({
where: {
id: invoiceId,
pos_id,
},
select: {
id: true,
type: true,
invoice_date: true,
settlement_type: true,
customer_id: true,
main_id: true,
last_tsp_status: true,
referenced_by: {
select: {
id: true,
},
},
customer: {
select: {
type: true,
},
},
items: {
select: {
id: true,
good_id: true,
service_id: true,
quantity: true,
unit_price: true,
total_amount: true,
discount_amount: true,
tax_amount: true,
payload: true,
notes: true,
},
},
},
})
if (!relatedInvoice) {
throw new NotFoundException('صورت‌حساب مورد نظر شما یافت نشد.')
}
if (relatedInvoice.last_tsp_status !== TspProviderResponseStatus.SUCCESS) {
throw new BadRequestException('امکان برگشت از خرید روی این صورت‌حساب وجود ندارد.')
}
if (relatedInvoice.referenced_by) {
throw new BadRequestException(
'این صورت‌حساب قبلا با یک صورت‌حساب دیگر جایگزین شده است.',
)
}
const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date)
const originalInvoiceDate = this.normalizeInvoiceDate(relatedInvoice.invoice_date)
const originalItemsByKey = new Map(
relatedInvoice.items.map(item => [this.getReturnItemKey(item), item]),
)
const usedItemKeys = new Set<string>()
const normalizedItems = data.items.map(item => {
const itemKey = this.getReturnItemKey(item)
if (!itemKey) {
throw new BadRequestException('هر قلم باید شناسه کالا یا خدمت داشته باشد.')
}
// if (usedItemKeys.has(itemKey)) {
// throw new BadRequestException('اقلام تکراری در صورت‌حساب بازگشتی مجاز نیستند.')
// }
usedItemKeys.add(itemKey)
const originalItem = originalItemsByKey.get(itemKey)
if (!originalItem) {
throw new BadRequestException(
'فقط اقلام موجود در صورت‌حساب اصلی قابل بازگشت هستند.',
)
}
const originalQuantity = Number(originalItem.quantity)
const requestedQuantity = Number(item.quantity)
if (requestedQuantity <= 0) {
throw new BadRequestException(
'تعداد/مقدار اقلام بازگشتی باید بیشتر از صفر باشد.',
)
}
if (requestedQuantity > originalQuantity) {
throw new BadRequestException(
'تعداد/مقدار اقلام بازگشتی نمی‌تواند از مقدار صورت‌حساب اصلی بیشتر باشد.',
)
}
const quantityRatio = requestedQuantity / originalQuantity
return {
invoice_id: originalItem.id,
good_id: originalItem.good_id,
service_id: originalItem.service_id || undefined,
quantity: requestedQuantity,
unit_price: Number(originalItem.unit_price),
total_amount: this.roundAmount(
Number(originalItem.total_amount) * quantityRatio,
),
discount_amount: this.roundAmount(
Number(originalItem.discount_amount || 0) * quantityRatio,
),
tax_amount: this.roundAmount(
Number(originalItem.tax_amount || 0) * quantityRatio,
),
payload: originalItem.payload
? JSON.parse(JSON.stringify(originalItem.payload))
: undefined,
notes: originalItem.notes || undefined,
}
})
if (!normalizedItems.length) {
throw new BadRequestException(
'حداقل یک قلم باید در صورت‌حساب بازگشتی باقی بماند.',
)
}
const hasRemovedItem = relatedInvoice.items.some(
item => !usedItemKeys.has(this.getReturnItemKey(item)),
)
const hasChangedQuantity = normalizedItems.some(item => {
const originalItem = relatedInvoice.items.find(
relatedItem =>
this.getReturnItemKey(relatedItem) === this.getReturnItemKey(item),
)
if (!originalItem) {
return false
}
return Number(originalItem.quantity) !== Number(item.quantity)
})
const hasChangedInvoiceDate = normalizedInvoiceDate !== originalInvoiceDate
if (!hasRemovedItem && !hasChangedQuantity && !hasChangedInvoiceDate) {
throw new BadRequestException('مقداری تغییر نکرده است.')
}
const totalAmount = normalizedItems.reduce(
(sum, item) => sum + Number(item.total_amount),
0,
)
const discountAmount = normalizedItems.reduce(
(sum, item) => sum + Number(item.discount_amount || 0),
0,
)
const taxAmount = normalizedItems.reduce(
(sum, item) => sum + Number(item.tax_amount || 0),
0,
)
const newInvoice = await this.sharedSaleInvoiceCreateService.create({
tx,
data: {
invoice_date: new Date(normalizedInvoiceDate),
items: normalizedItems as any,
total_amount: totalAmount,
discount_amount: discountAmount,
tax_amount: taxAmount,
customer_id: relatedInvoice.customer_id || undefined,
customer_type: relatedInvoice.customer?.type || CustomerType.UNKNOWN,
settlement_type: relatedInvoice.settlement_type,
payments: undefined,
} as any,
businessId: business_id,
complexId: complex_id,
posId: pos_id,
consumerAccountId,
main_invoice_id: relatedInvoice.main_id || relatedInvoice.id,
ref_invoice_id: relatedInvoice.id,
type: TspProviderRequestType.RETURN,
})
await tx.salesInvoice.update({
where: {
id: relatedInvoice.id,
},
data: {
referenced_by: {
connect: {
id: newInvoice.id,
},
},
},
})
return newInvoice
})
return this.salesInvoiceTspService.returnFromSaleSend(
pos_id,
business_id,
newInvoice.id,
)
}
private normalizeInvoiceDate(invoiceDate: Date | string) {
return new Date(invoiceDate).toISOString()
}
private getItemKey(item: { good_id?: string | null; service_id?: string | null }) {
return item.good_id || item.service_id || ''
}
private getPaymentsAmount(payments?: {
terminals?: { amount?: number }
cash?: number
set_off?: number
card?: number
bank?: number
check?: number
other?: number
}) {
return (
Number(payments?.terminals?.amount || 0) +
Number(payments?.cash || 0) +
Number(payments?.set_off || 0) +
Number(payments?.card || 0) +
Number(payments?.bank || 0) +
Number(payments?.check || 0) +
Number(payments?.other || 0)
)
}
private roundAmount(amount: number) {
return Number(amount.toFixed(2))
}
async inquiry(consumerAccountId: string, posId: string, invoiceId: string) {
const consumerId = await this.saleInvoiceAccessService.getConsumerIdWithPosAccess(
consumerAccountId,
posId,
)
return this.salesInvoiceTspService.get(invoiceId, posId, consumerId)
}
private getReturnItemKey(item: {
good_id?: string | null
service_id?: string | null
}) {
return item.good_id || item.service_id || ''
}
}
@@ -0,0 +1,247 @@
import type { SaleInvoiceType } from '@/common/interfaces/sale-invoice-payload'
import { ApiProperty, OmitType } from '@nestjs/swagger'
import { Type } from 'class-transformer'
import {
ArrayMinSize,
IsBoolean,
IsDate,
IsDateString,
IsEnum,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
Min,
ValidateNested,
} from 'class-validator'
import {
CustomerIndividual,
CustomerLegal,
InvoiceSettlementType,
} from 'generated/prisma/client'
import { CustomerType } from 'generated/prisma/enums'
export class SharedCreateSalesInvoiceItemDto {
@IsNumber()
@ApiProperty({ required: true })
unit_price: number
@IsNumber()
@ApiProperty({ required: true, default: 1 })
quantity: number
@IsNumber()
@ApiProperty({ required: true })
discount_amount: number
@IsNumber()
@ApiProperty({ required: true })
tax_amount: number
@IsNumber()
@ApiProperty({ required: true })
total_amount: number
@IsString()
@ApiProperty({ required: true })
invoice_id: string
@IsOptional()
@IsString()
@ApiProperty({ required: false })
good_id?: string
@IsOptional()
@IsString()
@ApiProperty({ required: false })
service_id?: string
// @IsEnum(SalesInvoiceItemPricingModel)
// @ApiProperty({ enum: Object.values(SalesInvoiceItemPricingModel) })
// @IsOptional()
// pricingModel: SalesInvoiceItemPricingModel
@IsOptional()
@IsObject()
@ApiProperty({ required: false, default: {} })
payload: SaleInvoiceType
@IsOptional()
@IsString()
@ApiProperty({ required: false, default: '' })
notes: string
}
export class SharedCreateTerminalPayment {
@IsOptional()
@IsNumber()
@Min(0)
@ApiProperty({ required: false, default: 0 })
amount?: number
@IsString()
@IsNotEmpty()
@ApiProperty({ required: true })
terminal_id: string
@IsString()
@IsNotEmpty()
@ApiProperty({ required: true })
stan: string
@IsString()
@IsNotEmpty()
@ApiProperty({ required: true })
rrn: string
@IsString()
@IsOptional()
@ApiProperty({ required: true })
response_code?: string
@IsString()
@IsOptional()
@ApiProperty({ required: true })
customer_card_no?: string
@Type(() => Date)
@IsDate()
@ApiProperty({ required: true })
transaction_date_time: Date
@IsOptional()
@IsString()
@ApiProperty({ required: false })
description?: string
}
export class SharedCreateSalesInvoicePaymentsDto {
@IsOptional()
@ValidateNested()
@Type(() => SharedCreateTerminalPayment)
@ApiProperty({ required: false, type: () => SharedCreateTerminalPayment })
terminals?: SharedCreateTerminalPayment
@IsOptional()
@IsNumber()
@Min(0)
@ApiProperty({ required: false, default: 0 })
cash?: number
@IsOptional()
@IsNumber()
@Min(0)
@ApiProperty({ required: false, default: 0 })
set_off?: number
@IsOptional()
@IsNumber()
@Min(0)
@ApiProperty({ required: false, default: 0 })
card?: number
@IsOptional()
@IsNumber()
@Min(0)
@ApiProperty({ required: false, default: 0 })
bank?: number
@IsOptional()
@IsNumber()
@Min(0)
@ApiProperty({ required: false, default: 0 })
check?: number
@IsOptional()
@IsNumber()
@Min(0)
@ApiProperty({ required: false, default: 0 })
other?: number
}
export class SharedCreateSalesInvoiceDto {
// @TODO: totalAmount must calculated instead of get from api
@IsNumber()
@ApiProperty({ required: true, default: 0 })
total_amount: number
@IsNumber()
@ApiProperty({ required: true, default: 0 })
discount_amount: number
@IsNumber()
@ApiProperty({ required: true, default: 0 })
tax_amount: number
@ApiProperty({ required: true })
@IsDateString(
{ strict: true },
{ message: 'invoice_date must be a valid ISO-8601 string' },
)
invoice_date: Date
@ApiProperty({ required: true, enum: InvoiceSettlementType })
@IsEnum(InvoiceSettlementType)
settlement_type: InvoiceSettlementType
@ApiProperty({ required: true })
@IsObject()
@ValidateNested({ each: true })
@Type(() => SharedCreateSalesInvoicePaymentsDto)
payments: SharedCreateSalesInvoicePaymentsDto
@ApiProperty()
@ArrayMinSize(1)
@ValidateNested({ each: true })
items: SharedCreateSalesInvoiceItemDto[]
@ApiProperty({ required: false })
@IsOptional()
@IsString()
ref_invoice_id?: string
@ApiProperty({ required: false })
@IsOptional()
@IsString()
notes?: string
@ApiProperty({ required: false, default: false })
@IsOptional()
@IsBoolean()
send_to_tsp?: boolean
@ApiProperty({ required: true, default: CustomerType.UNKNOWN })
@IsEnum(CustomerType)
customer_type: CustomerType
@IsOptional()
@IsString()
@ApiProperty({ required: false })
customer_id?: string
@ApiProperty({ required: false })
@IsOptional()
@IsObject()
customer?: {
customer_individual?: Omit<CustomerIndividual, 'business_activity_id' | 'customer_id'>
customer_legal?: Omit<CustomerLegal, 'business_activity_id' | 'customer_id'>
customer_unknown?: {
first_name: string
last_name: string
}
}
}
export class SharedCorrectionSalesInvoiceDto extends OmitType(
SharedCreateSalesInvoiceDto,
['customer', 'customer_id', 'customer_type', 'settlement_type'],
) {}
export class SharedReturnSalesInvoiceDto extends OmitType(SharedCreateSalesInvoiceDto, [
'customer',
'customer_id',
'customer_type',
'settlement_type',
'payments',
]) {}
@@ -0,0 +1,574 @@
import { QUERY_CONSTANTS } from '@/common/queryConstants'
import { SalesInvoiceCreateInput } from '@/generated/prisma/models'
import { PrismaService } from '@/prisma/prisma.service'
import { BadRequestException, Injectable } from '@nestjs/common'
import { Prisma } from 'generated/prisma/client'
import {
CustomerType,
PaymentMethodType,
TspProviderRequestType,
} from 'generated/prisma/enums'
import { SharedCreateSalesInvoiceDto } from './sale-invoice-create.dto'
interface TerminalPaymentInfo {
terminal_id: string
stan: string
rrn: string
transaction_date_time: string | Date
customer_card_no: string
description?: string
amount: number
}
interface NormalizedPayment {
method: PaymentMethodType
amount: number
terminalInfo?: TerminalPaymentInfo
}
interface CreateSharedSaleInvoiceInput {
tx?: Prisma.TransactionClient
data: SharedCreateSalesInvoiceDto
businessId: string
complexId: string
posId: string
consumerAccountId: string
type: TspProviderRequestType
main_invoice_id?: string
ref_invoice_id?: string
}
@Injectable()
export class SharedSaleInvoiceCreateService {
private readonly createInvoiceRetries = 3
constructor(private prisma: PrismaService) {}
async create(input: CreateSharedSaleInvoiceInput) {
const {
tx = this.prisma,
data,
businessId,
complexId,
posId,
consumerAccountId,
type,
main_invoice_id,
ref_invoice_id,
} = input
const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date)
const payments =
type === TspProviderRequestType.ORIGINAL || data.payments
? this.buildPaymentsData(data.payments, data.total_amount)
: []
for (let attempt = 1; attempt <= this.createInvoiceRetries; attempt++) {
try {
return await tx.$transaction(async $tx => {
const invoiceNumber = await this.getNextInvoiceNumber($tx, businessId)
const customerId = await this.resolveCustomerId($tx, data, businessId)
const goodsById = await this.getGoodsById($tx, data)
const salesInvoiceData = this.buildSalesInvoiceData({
data,
normalizedInvoiceDate,
invoiceNumber,
consumerAccountId,
businessId,
complexId,
posId,
goodsById,
customerId,
type,
main_invoice_id,
ref_invoice_id,
})
const salesInvoice = await $tx.salesInvoice.create({
data: salesInvoiceData,
select: { ...QUERY_CONSTANTS.SALE_INVOICE.select },
})
if (payments.length) {
await this.createPayments(
$tx,
salesInvoice.id,
payments,
normalizedInvoiceDate,
)
}
return salesInvoice
})
} catch (error) {
if (
this.isRetryableInvoiceConflict(error) &&
attempt < this.createInvoiceRetries
) {
continue
}
throw error
}
}
throw new BadRequestException('ایجاد صورت‌حساب با خطا مواجه شد.')
}
private isRetryableInvoiceConflict(error: unknown) {
if (!(error instanceof Prisma.PrismaClientKnownRequestError)) {
return false
}
if (error.code !== 'P2002') {
return false
}
const target = (error.meta?.target as string[]) || []
return (
target.includes('invoice_number') ||
target.includes('sales_invoices_invoice_number_pos_id_key')
)
}
private normalizeInvoiceDate(invoiceDate: Date | string) {
return new Date(invoiceDate).toISOString() as any
}
private buildPaymentsData(
paymentsData: SharedCreateSalesInvoiceDto['payments'],
totalAmount: number,
) {
const paymentMethodMap: Record<string, PaymentMethodType> = {
cash: PaymentMethodType.CASH,
set_off: PaymentMethodType.SET_OFF,
card: PaymentMethodType.CARD,
payment_gateway: PaymentMethodType.PAYMENT_GATEWAY,
bank: PaymentMethodType.BANK,
check: PaymentMethodType.CHEQUE,
other: PaymentMethodType.OTHER,
terminal: PaymentMethodType.TERMINAL,
}
const rawPayments = (paymentsData || {}) as Record<string, unknown>
const terminalPayments = rawPayments.terminals as TerminalPaymentInfo[] | undefined
const payments: NormalizedPayment[] = Object.entries(rawPayments)
.filter(([key, value]) => key !== 'terminals' && value && Number(value) > 0)
.map(([key, value]) => ({
method: paymentMethodMap[key.toLowerCase()],
amount: typeof value === 'number' ? value : Number(value || 0),
}))
.filter(payment => payment.method && payment.amount > 0) as NormalizedPayment[]
const hasTerminalPayment = terminalPayments && terminalPayments.length
if (hasTerminalPayment) {
for (const terminal of terminalPayments) {
payments.push({
method: PaymentMethodType.TERMINAL,
amount: terminal.amount,
terminalInfo: terminal,
})
}
}
//TODO: for correction i need to validate payments with diff of total amount and original invoice amount
// this.validatePayments(payments, totalAmount)
return payments
}
private validatePayments(payments: NormalizedPayment[], totalAmount: number) {
const totalPayments = payments.reduce((sum, payment) => sum + payment.amount, 0)
const roundedTotalPayments = Number(totalPayments.toFixed(2))
const roundedTotalAmount = Number(Number(totalAmount).toFixed(2))
if (roundedTotalPayments !== roundedTotalAmount) {
throw new BadRequestException('مبلغ پرداختی باید برابر با مبلغ کل صورت‌حساب باشد.')
}
}
private async resolveCustomerId(
tx: Prisma.TransactionClient,
data: SharedCreateSalesInvoiceDto,
businessId: string,
) {
const { customer_id, customer_type, customer } = data
if (customer_id) {
return customer_id
}
if (customer_type === CustomerType.INDIVIDUAL && customer?.customer_individual) {
const { national_id, postal_code, economic_code, ...rest } =
customer.customer_individual
const foundedCustomer = await tx.customerIndividual.findFirst({
where: {
business_activity_id: businessId,
OR: [
{
economic_code,
},
{
postal_code,
national_id,
},
],
},
select: {
customer_id: true,
},
})
let customerIndividualId: string | undefined = foundedCustomer?.customer_id
if (foundedCustomer) {
await tx.customerIndividual.update({
where: {
customer_id: foundedCustomer.customer_id,
},
data: {
...customer.customer_individual,
},
})
} else {
const createdCustomer = await tx.customerIndividual.create({
data: {
...customer.customer_individual,
customer: {
create: {
type: CustomerType.INDIVIDUAL,
},
},
business_activity: {
connect: {
id: businessId,
},
},
},
})
customerIndividualId = createdCustomer.customer_id
}
if (!customerIndividualId) {
throw new BadRequestException('متاسفانه مشکلی در اطلاعات مشتری وجود دارد.')
}
return customerIndividualId
} else if (customer_type === CustomerType.LEGAL && customer?.customer_legal) {
const { registration_number, economic_code, postal_code } = customer.customer_legal
const foundedCustomer = await tx.customerLegal.findFirst({
where: {
business_activity_id: businessId,
OR: [
{
economic_code,
},
{
postal_code,
registration_number,
},
],
},
select: {
customer_id: true,
},
})
let customerLegalId: string | undefined = foundedCustomer?.customer_id
if (foundedCustomer) {
await tx.customerLegal.update({
where: {
customer_id: foundedCustomer.customer_id,
},
data: {
...customer.customer_legal,
},
})
} else {
const createdCustomer = await tx.customerLegal.create({
data: {
...customer.customer_legal,
customer: {
create: {
type: CustomerType.LEGAL,
},
},
business_activity: {
connect: {
id: businessId,
},
},
},
})
customerLegalId = createdCustomer.customer_id
}
if (!customerLegalId) {
throw new BadRequestException('متاسفانه مشکلی در اطلاعات مشتری وجود دارد.')
}
return customerLegalId
}
return null
}
private async getGoodsById(
tx: Prisma.TransactionClient,
data: SharedCreateSalesInvoiceDto,
) {
const itemGoodIds = data.items
.map(item => item.good_id)
.filter((goodId): goodId is string => Boolean(goodId))
const goods = itemGoodIds.length
? await tx.good.findMany({
where: {
id: {
in: itemGoodIds,
},
},
select: {
id: true,
name: true,
sku: {
select: {
id: true,
name: true,
code: true,
VAT: true,
},
},
local_sku: true,
barcode: true,
pricing_model: true,
measure_unit: {
select: {
id: true,
name: true,
code: true,
},
},
base_sale_price: true,
image_url: true,
category: {
select: {
id: true,
name: true,
},
},
},
})
: []
return new Map(goods.map(good => [good.id, good]))
}
private buildSalesInvoiceData(params: {
data: SharedCreateSalesInvoiceDto
normalizedInvoiceDate: Date
invoiceNumber: number
consumerAccountId: string
businessId: string
complexId: string
posId: string
goodsById: Map<string, any>
customerId: string | null
type: TspProviderRequestType
main_invoice_id?: string
ref_invoice_id?: string
}) {
const {
data,
normalizedInvoiceDate,
invoiceNumber,
consumerAccountId,
posId,
goodsById,
customerId,
businessId,
complexId,
type,
main_invoice_id,
ref_invoice_id,
} = params
const {
customer_id,
customer_type,
customer,
payments,
settlement_type,
send_to_tsp,
...invoiceData
} = data
if (
type !== TspProviderRequestType.ORIGINAL &&
!(main_invoice_id || ref_invoice_id)
) {
throw new BadRequestException('متاسفانه مشکلی در اطلاعات صورت‌حساب وجود دارد.')
}
const salesInvoiceData: SalesInvoiceCreateInput = {
...invoiceData,
invoice_date: normalizedInvoiceDate,
invoice_number: invoiceNumber,
total_amount: data.total_amount,
discount_amount: data.items.reduce(
(prev, curr) => (prev += curr.discount_amount),
0,
),
tax_amount: data.items.reduce((prev, curr) => (prev += curr.tax_amount), 0),
code: this.generateInvoiceCode(businessId, complexId, posId, invoiceNumber),
type,
settlement_type,
items: {
createMany: {
data: data.items.map(item => ({
good_id: item.good_id!,
quantity: item.quantity,
unit_price: item.unit_price,
total_amount: item.total_amount,
discount_amount: item.discount_amount,
tax_amount: item.tax_amount,
measure_unit_text: goodsById.get(item.good_id!)?.measure_unit.name || null,
measure_unit_code: goodsById.get(item.good_id!)?.measure_unit.code || null,
sku_code: goodsById.get(item.good_id!)?.sku.code || null,
sku_vat: goodsById.get(item.good_id!)?.sku.VAT || null,
payload: item.payload ? JSON.parse(JSON.stringify(item.payload)) : undefined,
good_snapshot: item.good_id
? JSON.parse(JSON.stringify(goodsById.get(item.good_id) || null))
: undefined,
})),
},
},
unknown_customer: {},
consumer_account: {
connect: {
id: consumerAccountId,
},
},
pos: {
connect: {
id: posId,
},
},
}
if (customerId) {
salesInvoiceData.customer = {
connect: {
id: customerId,
},
}
} else if (data.customer?.customer_unknown) {
salesInvoiceData.unknown_customer = data.customer.customer_unknown
}
if (type !== TspProviderRequestType.ORIGINAL) {
salesInvoiceData.main_id = main_invoice_id
salesInvoiceData.reference_invoice = {
connect: {
id: ref_invoice_id,
},
}
}
return salesInvoiceData
}
async getNextInvoiceNumber(tx: Prisma.TransactionClient, businessId: string) {
const latestInvoice = await tx.salesInvoice.findFirst({
where: {
pos: {
complex: {
business_activity_id: businessId,
},
},
},
orderBy: {
invoice_number: 'desc',
},
select: {
invoice_number: true,
pos: {
select: {
complex: {
select: {
business_activity: {
select: {
invoice_number_sequence: true,
},
},
},
},
},
},
},
})
const latestSequence =
latestInvoice?.pos?.complex?.business_activity?.invoice_number_sequence.toNumber() ||
0
return Math.max(latestInvoice?.invoice_number || 0, latestSequence) + 1
}
private async createPayments(
tx: Prisma.TransactionClient,
invoiceId: string,
payments: NormalizedPayment[],
paidAt: Date,
) {
for (const payment of payments) {
if (payment.amount <= 0) {
continue
}
const createdPayment = await tx.salesInvoicePayment.create({
data: {
invoice_id: invoiceId,
amount: payment.amount,
payment_method: payment.method,
paid_at: paidAt,
},
})
if (payment.method === PaymentMethodType.TERMINAL && payment.terminalInfo) {
const {
terminal_id,
stan,
rrn,
transaction_date_time,
customer_card_no,
description,
} = payment.terminalInfo
await tx.salesInvoicePaymentTerminalInfo.create({
data: {
payment_id: createdPayment.id,
terminal_id,
stan: stan,
rrn: rrn,
transaction_date_time: transaction_date_time
? new Date(transaction_date_time)
: new Date(),
customer_card_no: '1234567890123456',
// customer_card_no: customer_card_no || null,
description: description || null,
},
})
}
}
}
private generateInvoiceCode(
businessId: string,
complexId: string,
posId: string,
invoiceNumber: number,
) {
return `${businessId.substring(businessId.length - 2, businessId.length)}${complexId.substring(complexId.length - 2, complexId.length)}${posId.substring(posId.length - 2, posId.length)}${invoiceNumber.toString()}`
}
}
@@ -0,0 +1,90 @@
import { ApiPropertyOptional } from '@nestjs/swagger'
import { Type } from 'class-transformer'
import {
IsDateString,
IsEnum,
IsNumber,
IsOptional,
IsString,
Min,
} from 'class-validator'
import { TspProviderResponseStatus } from 'generated/prisma/enums'
export class SharedSaleInvoicesFilterDto {
@ApiPropertyOptional({ default: 1 })
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(1)
page?: number
@ApiPropertyOptional({ default: 10 })
@IsOptional()
@Type(() => Number)
@IsNumber()
@Min(1)
perPage?: number
@ApiPropertyOptional()
@IsOptional()
@IsDateString()
invoice_date_from?: string
@ApiPropertyOptional()
@IsOptional()
@IsDateString()
invoice_date_to?: string
@ApiPropertyOptional()
@IsOptional()
@IsDateString()
created_at_from?: string
@ApiPropertyOptional()
@IsOptional()
@IsDateString()
created_at_to?: string
@ApiPropertyOptional()
@IsOptional()
@IsString()
customer_name?: string
@ApiPropertyOptional()
@IsOptional()
@IsString()
customer_mobile?: string
@ApiPropertyOptional()
@IsOptional()
@IsString()
customer_national_id?: string
@ApiPropertyOptional()
@IsOptional()
@IsString()
customer_economic_code?: string
@ApiPropertyOptional({ enum: TspProviderResponseStatus })
@IsOptional()
@IsEnum(TspProviderResponseStatus)
status?: TspProviderResponseStatus
@ApiPropertyOptional()
@IsOptional()
@Type(() => Number)
@IsNumber()
total_amount?: number
@ApiPropertyOptional()
@IsOptional()
@Type(() => Number)
@IsNumber()
total_amount_from?: number
@ApiPropertyOptional()
@IsOptional()
@Type(() => Number)
@IsNumber()
total_amount_to?: number
}
@@ -0,0 +1,137 @@
import { TspProviderResponseStatus } from '@/generated/prisma/enums'
import { SalesInvoiceWhereInput } from '@/generated/prisma/models'
import { Injectable } from '@nestjs/common'
import { SharedSaleInvoicesFilterDto } from './sale-invoice-filter.dto'
@Injectable()
export class SharedSaleInvoiceFilterService {
buildWhere(filter: SharedSaleInvoicesFilterDto): SalesInvoiceWhereInput {
const where: SalesInvoiceWhereInput = {}
if (filter.invoice_date_from || filter.invoice_date_to) {
where.invoice_date = {
...(filter.invoice_date_from ? { gte: new Date(filter.invoice_date_from) } : {}),
...(filter.invoice_date_to ? { lte: new Date(filter.invoice_date_to) } : {}),
}
}
if (filter.created_at_from || filter.created_at_to) {
where.created_at = {
...(filter.created_at_from ? { gte: new Date(filter.created_at_from) } : {}),
...(filter.created_at_to ? { lte: new Date(filter.created_at_to) } : {}),
}
}
if (
filter.total_amount !== undefined ||
filter.total_amount_from !== undefined ||
filter.total_amount_to !== undefined
) {
where.total_amount = {
...(filter.total_amount !== undefined ? { equals: filter.total_amount } : {}),
...(filter.total_amount_from !== undefined
? { gte: filter.total_amount_from }
: {}),
...(filter.total_amount_to !== undefined ? { lte: filter.total_amount_to } : {}),
}
}
if (
filter.customer_name?.trim() ||
filter.customer_mobile?.trim() ||
filter.customer_national_id?.trim() ||
filter.customer_economic_code?.trim()
) {
where.customer = {
is: {
OR: [
...(filter.customer_name?.trim()
? [
{
individual: {
is: {
OR: [
{ first_name: { contains: filter.customer_name.trim() } },
{ last_name: { contains: filter.customer_name.trim() } },
],
},
},
},
{
legal: {
is: {
name: {
contains: filter.customer_name.trim(),
},
},
},
},
]
: []),
...(filter.customer_mobile?.trim()
? [
{
individual: {
is: {
mobile_number: { contains: filter.customer_mobile.trim() },
},
},
},
]
: []),
...(filter.customer_national_id?.trim()
? [
{
individual: {
is: {
national_id: { contains: filter.customer_national_id.trim() },
},
},
},
]
: []),
...(filter.customer_economic_code?.trim()
? [
{
legal: {
is: {
OR: [
{
economic_code: {
contains: filter.customer_economic_code.trim(),
},
},
{
registration_number: {
contains: filter.customer_economic_code.trim(),
},
},
],
},
},
},
]
: []),
],
},
}
}
if (filter.status) {
if (filter.status === TspProviderResponseStatus.NOT_SEND) {
where.OR = [
{
last_tsp_status: null,
},
{
last_tsp_status: TspProviderResponseStatus.NOT_SEND,
},
]
} else {
where.last_tsp_status = filter.status
}
}
return where
}
}
@@ -0,0 +1,29 @@
import { Injectable } from '@nestjs/common'
@Injectable()
export class SharedSaleInvoicePaginationService {
normalize(
page: number = 1,
perPage: number = 10,
defaultPerPage: number = 10,
maxPerPage: number = 50,
) {
const normalizedPageValue = Number(page ?? 1)
const normalizedPage = Number.isFinite(normalizedPageValue)
? Math.max(1, Math.floor(normalizedPageValue))
: 1
const requestedPerPageValue = Number(perPage ?? defaultPerPage)
const requestedPerPage = Number.isFinite(requestedPerPageValue)
? Math.max(1, Math.floor(requestedPerPageValue))
: defaultPerPage
const normalizedPerPage = Math.min(requestedPerPage, maxPerPage)
return {
page: normalizedPage,
perPage: normalizedPerPage,
skip: (normalizedPage - 1) * normalizedPerPage,
take: normalizedPerPage,
}
}
}
+90
View File
@@ -0,0 +1,90 @@
import dayjs, { Dayjs } from 'dayjs'
import 'dayjs/locale/fa'
import relativeTime from 'dayjs/plugin/relativeTime'
import jalaliPlugin from 'jalaliday/dayjs'
dayjs.extend(jalaliPlugin)
dayjs.extend(relativeTime)
export function toJalali(date: string | number | Date | Dayjs): Date {
return dayjs(date).calendar('jalali').toDate()
}
export function toGregorian(date: string | number | Date | Dayjs): Date {
return dayjs(date).calendar('gregory').toDate()
}
export function getCurrentJalaliSeasonStart(
baseDate: string | number | Date | Dayjs = dayjs(),
): Date {
const date = dayjs(baseDate).calendar('jalali')
const month = date.month() + 1
const seasonStartMonth = Math.floor((month - 1) / 3) * 3 + 1
return date
.month(seasonStartMonth - 1)
.startOf('month')
.startOf('day')
.toDate()
}
export function getCurrentJalaliSeasonEnd(
baseDate: string | number | Date | Dayjs = dayjs(),
): Date {
return dayjs(getCurrentJalaliSeasonStart(baseDate))
.add(3, 'month')
.endOf('month')
.endOf('day')
.toDate()
}
export function getCurrentGregorianSeasonStart(
baseDate: string | number | Date | Dayjs = dayjs(),
): Date {
const date = dayjs(baseDate).calendar('gregory')
const month = date.month() + 1
const seasonStartMonth = Math.floor((month - 1) / 3) * 3 + 1
return date
.month(seasonStartMonth - 1)
.startOf('month')
.startOf('day')
.toDate()
}
export function getCurrentGregorianSeasonEnd(
baseDate: string | number | Date | Dayjs = dayjs(),
): Date {
return dayjs(getCurrentGregorianSeasonStart(baseDate))
.add(2, 'month')
.endOf('month')
.endOf('day')
.toDate()
}
export function getCurrentJalaliSeasonRange(
baseDate: string | number | Date | Dayjs = dayjs(),
): { start: Date; end: Date } {
return {
start: getCurrentJalaliSeasonStart(baseDate),
end: getCurrentJalaliSeasonEnd(baseDate),
}
}
export function getCurrentGregorianSeasonRange(
baseDate: string | number | Date | Dayjs = dayjs(),
): { start: Date; end: Date } {
return {
start: getCurrentGregorianSeasonStart(baseDate),
end: getCurrentGregorianSeasonEnd(baseDate),
}
}
export default {
toJalali,
toGregorian,
getCurrentJalaliSeasonStart,
getCurrentJalaliSeasonEnd,
getCurrentGregorianSeasonStart,
getCurrentGregorianSeasonEnd,
getCurrentJalaliSeasonRange,
getCurrentGregorianSeasonRange,
}
+29
View File
@@ -0,0 +1,29 @@
import translates from '@/common/constants/translates/translates'
type EnumTranslateMap = Record<string, string>
type EnumsRegistry = Record<string, EnumTranslateMap>
export interface EnumTranslatedValue {
value: string | null | undefined
translate: string | null
}
export function translateEnumValue(
enumKey: keyof typeof translates.enums,
value: string | null | undefined,
): EnumTranslatedValue {
const enumsRegistry = translates.enums as EnumsRegistry
const enumMap = enumsRegistry[String(enumKey)]
if (!value) {
return {
value,
translate: null,
}
}
return {
value,
translate: enumMap?.[value] ?? null,
}
}
@@ -0,0 +1,14 @@
import { Matches, MinLength } from 'class-validator'
const MIN_LENGTH = 6
const REGEX = /^[a-zA-Z0-9]*$/
export const FiscalIdFieldValidator = () =>
function (target: object, propertyKey: string) {
MinLength(MIN_LENGTH, {
message: `شناسه یکتا باید حداقل ${MIN_LENGTH} کاراکتر باشد`,
})(target, propertyKey)
Matches(REGEX, {
message: 'شناسه یکتا فقط می‌تواند شامل حروف و اعداد باشد',
})(target, propertyKey)
}
@@ -0,0 +1,3 @@
export { FiscalIdFieldValidator } from './fiscalId-validator'
export { PasswordFieldValidator, PASSWORD_MIN_LENGTH } from './password'
export { UsernameFieldValidator } from './username'
@@ -0,0 +1,10 @@
import { MinLength } from 'class-validator'
export const PASSWORD_MIN_LENGTH = 6
export const PasswordFieldValidator = () =>
function (target: object, propertyKey: string) {
MinLength(PASSWORD_MIN_LENGTH, {
message: `رمز عبور باید حداقل ${PASSWORD_MIN_LENGTH} کاراکتر باشد`,
})(target, propertyKey)
}
@@ -0,0 +1,14 @@
import { Matches, MinLength } from 'class-validator'
const USERNAME_REGEX = /^[a-zA-Z0-9_-]*$/
const USERNAME_MIN_LENGTH = 6
export const UsernameFieldValidator = () =>
function (target: object, propertyKey: string) {
MinLength(USERNAME_MIN_LENGTH, {
message: `نام کاربری باید حداقل ${USERNAME_MIN_LENGTH} کاراکتر باشد`,
})(target, propertyKey)
Matches(USERNAME_REGEX, {
message: 'نام کاربری فقط می‌تواند شامل حروف، اعداد، زیرخط و خط تیره باشد',
})(target, propertyKey)
}
+45
View File
@@ -0,0 +1,45 @@
import {
FetchRequestContext,
FetchRequestInterceptor,
} from '@/common/interceptors/fetch-request.interceptor'
import { Injectable } from '@nestjs/common'
@Injectable()
export class HttpClientUtil {
async request(
url: string,
init: RequestInit = {},
interceptors: FetchRequestInterceptor[] = [],
): Promise<Response> {
let context: FetchRequestContext = { url, init }
for (const interceptor of interceptors) {
if (!interceptor.onRequest) {
continue
}
context = await interceptor.onRequest(context)
}
try {
let response = await fetch(context.url, context.init)
for (const interceptor of interceptors) {
if (!interceptor.onResponse) {
continue
}
response = await interceptor.onResponse(context, response)
}
return response
} catch (error: any) {
for (const interceptor of interceptors) {
if (!interceptor.onError) {
continue
}
await interceptor.onError(context, error)
}
throw error
}
}
}
+9
View File
@@ -0,0 +1,9 @@
export * from './date-formatter.utils'
export * from './enum-translator.util'
export * from './field-validator.util'
export * from './http-client.util'
export * from './jwt-user.util'
export * from './mappers/consumer_mappers.util'
export * from './password.util'
export * from './redisKeyMaker'
export * from './tracking-code-generator.util'
+2 -2
View File
@@ -18,13 +18,13 @@ export function checkAndDecodeJwtToken(
token = authHeader.slice(7) token = authHeader.slice(7)
} }
} }
if (!token) throw new UnauthorizedException('Missing accessToken cookie') if (!token) throw new UnauthorizedException('توکن احراز هویت ارسال نشده است')
try { try {
const payload = jwtService.decode(token) as AccessTokenPayload const payload = jwtService.decode(token) as AccessTokenPayload
return payload as ITokenPayload return payload as ITokenPayload
} catch { } catch {
return null throw new UnauthorizedException('توکن احراز هویت نامعتبر است')
} }
} }
@@ -0,0 +1,37 @@
import { translateEnumValue } from '../enum-translator.util'
export default (consumer: any) => {
const { legal, individual, activation, type, status, _count, ...rest } = consumer
const { business_activities: business_counts } = _count || { business_activities: 0 }
const partner = legal?.partner || individual?.partner
delete legal?.partner
delete individual?.partner
const returnData = {
...rest,
partner,
type: translateEnumValue('ConsumerType', type),
status: translateEnumValue('ConsumerStatus', consumer.status),
legal: legal ? { ...legal } : null,
individual: individual
? { ...individual, fullname: `${individual?.first_name} ${individual?.last_name}` }
: null,
name: legal ? legal.name : `${individual?.first_name} ${individual?.last_name}`,
}
if (business_counts !== undefined) {
returnData['business_counts'] = business_counts
}
return returnData
}
function prepareLicenseInfo(latestLicense: any) {
if (!latestLicense) return null
const { license, ...rest } = latestLicense
return {
...rest,
}
}
+54
View File
@@ -0,0 +1,54 @@
import { BadRequestException, ConflictException } from '@nestjs/common'
import { Prisma } from 'generated/prisma/client'
type UniqueMessageMap = Record<string, string>
export class PrismaErrorUtil {
static isKnownError(error: unknown): error is Prisma.PrismaClientKnownRequestError {
return error instanceof Prisma.PrismaClientKnownRequestError
}
static isCode(error: unknown, code: string): boolean {
return this.isKnownError(error) && error.code === code
}
static hasTarget(error: unknown, targetKey: string): boolean {
if (!this.isKnownError(error)) {
return false
}
const target = error.meta?.target as string[] | string | undefined
if (Array.isArray(target)) {
return target.includes(targetKey)
}
if (typeof target === 'string') {
return target.includes(targetKey)
}
return false
}
static throwIfKnown(error: unknown, uniqueMap: UniqueMessageMap = {}) {
if (!(error instanceof Prisma.PrismaClientKnownRequestError)) {
throw error
}
if (error.code === 'P2002') {
const target = (error.meta?.target as string[]) || []
for (const key of Object.keys(uniqueMap)) {
if (target.includes(key)) {
throw new BadRequestException(uniqueMap[key])
}
}
throw new ConflictException('رکورد تکراری است.')
}
if (error.code === 'P2003') {
throw new BadRequestException('ارتباط داده‌ای نامعتبر است.')
}
if (error.code === 'P2025') {
throw new BadRequestException('رکورد مورد نظر یافت نشد.')
}
throw error
}
}
@@ -0,0 +1,20 @@
export class ConsumerKeyMaker {
static middleware(token: string): string {
return `consumer:middleware:${token}`
}
static consumerInfo(consumerId: string): string {
return `consumers:${consumerId}:info`
}
static consumerBusinessActivitiesList(consumerId: string): string {
return `consumers:${consumerId}:business-activities:list`
}
static consumerBusinessActivityInfo(
consumerId: string,
businessActivityId: string,
): string {
return `consumers:${consumerId}:business-activities:${businessActivityId}:info`
}
}
+9
View File
@@ -0,0 +1,9 @@
export class EnumKeyMaker {
static enumsAll(buildVersion: string): string {
return `enums:${buildVersion}:all`
}
static enumsValues(buildVersion: string, enumName: string): string {
return `enums:${buildVersion}:values:${enumName}`
}
}

Some files were not shown because too many files have changed in this diff Show More