name: CI/CD - ERP Core Monorepo

on:
  pull_request:
    branches:
      - production
    types:
      - closed

jobs:
  backend-build-and-deploy:
    name: Build & Deploy ERP Core Backend
    runs-on: linux
    if: github.event.pull_request.merged == true
    steps:
      - name: Checkout code
        run: |
          git clone \
            --depth 1 \
            --branch production \
            https://oauth2:${{ secrets.ERPCORE }}@gitea.hexdive.com/New_REP_SYSTEM/ERP-core.git \
            .

      - name: Restore backend
        run: dotnet restore Backend/ERPCore.sln

      - name: Publish backend
        run: |
          rm -rf /tmp/erpcore-deploy
          mkdir -p /tmp/erpcore-deploy
          dotnet publish Backend/ERPCore/ERPCore.csproj \
            -c Release \
            -r linux-x64 \
            --self-contained true \
            -o /tmp/erpcore-deploy

      - name: Stop backend service
        run: systemctl stop erpcore.service

      - name: Copy backend to deploy folder
        run: |
          mkdir -p /home/erp/core/publish
          rm -rf /home/erp/core/publish/*
          cp -r /tmp/erpcore-deploy/* /home/erp/core/publish/

      - name: Fix backend ownership and permissions
        run: |
          chown -R erpuser:erpuser /home/erp/core/publish
          find /home/erp/core/publish -type d -exec chmod 755 {} +
          find /home/erp/core/publish -type f -exec chmod 644 {} +
          chmod +x /home/erp/core/publish/ERPCore

      - name: Restart backend service
        run: systemctl restart erpcore.service

      - name: Verify backend
        run: |
          systemctl is-active erpcore.service && echo "Backend service running" || echo "Backend service failed"

  frontend-build-and-deploy:
    name: Build & Deploy ERP Core Frontend
    runs-on: linux
    needs: backend-build-and-deploy
    if: github.event.pull_request.merged == true
    steps:
      - name: Checkout code
        run: |
          git clone \
            --depth 1 \
            --branch Production \
            https://oauth2:${{ secrets.ERPCORE }}@gitea.hexdive.com/New_REP_SYSTEM/ERP-core.git \
            .

      - name: Build frontend Docker image
        run: docker build -t erpcore-frontend:latest Frontend/erp-system

      - name: Stop & remove existing frontend container
        run: |
          docker stop erpcore-frontend 2>/dev/null || true
          docker rm erpcore-frontend 2>/dev/null || true

      - name: Start frontend container
        run: |
          docker run -d \
            --name erpcore-frontend \
            -p 6702:6702 \
            --add-host=host.docker.internal:host-gateway \
            -e BACKEND_ORIGIN=http://host.docker.internal:6701 \
            --restart unless-stopped \
            --memory="512m" \
            --cpus="1.0" \
            --cap-drop ALL \
            --security-opt no-new-privileges \
            erpcore-frontend:latest

      - name: Remove old frontend images
        run: docker image prune -f

      - name: Verify frontend
        run: |
          docker ps --filter name=erpcore-frontend --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" \
            | grep erpcore-frontend && echo "Frontend container running" || echo "Frontend container failed"
This commit is contained in:
Kalana B. Thilakarathna
2026-09-17 13:59:05 +05:30
parent 93f2f1bd3b
commit 03d01d4c4e
61 changed files with 2195 additions and 1245 deletions
+56 -58
View File
@@ -1,101 +1,99 @@
import { api } from '@/lib/api'
import type {
PaginatedResponse,
SaleResponse,
OrderResponse,
OrderStatus,
ShopPOResponse,
InvoiceResponse,
InvoiceStatus,
PaymentResponse,
SaleStatus,
PaymentStatus,
PaymentMethod,
ShopPOStatus,
} from '@/types/api'
import type { PageParams } from './master'
// ─── Sales ────────────────────────────────────────────────────────────────────
// ─── Shop POs ─────────────────────────────────────────────────────────────────
export interface SaleItemBody {
export interface ShopPOItemBody {
itemId: string
quantity: number
unitPrice: number
unitPrice?: number | null
}
export interface SaleBody {
export interface CreateShopPOBody {
storeId: string
saleDate: string
rideId?: string | null
fulfillImmediately?: boolean
items: SaleItemBody[]
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
createdByUserId: string
items: ShopPOItemBody[]
}
export interface SaleListParams extends PageParams {
storeId?: string
status?: SaleStatus
paymentStatus?: PaymentStatus
rideId?: string
from?: string
to?: string
export interface UpdateShopPOItemsBody {
items: ShopPOItemBody[]
}
export const sales = {
list: (params?: SaleListParams) =>
api.get<PaginatedResponse<SaleResponse>>('/sales', { params }).then(r => r.data),
export interface ShopPOListParams extends PageParams {
agentId?: string
routeId?: string
shopId?: string
confirmed?: boolean
status?: ShopPOStatus
}
export const shopPos = {
list: (params?: ShopPOListParams) =>
api.get<PaginatedResponse<ShopPOResponse>>('/shop-pos', { params }).then(r => r.data),
get: (id: string) =>
api.get<SaleResponse>(`/sales/${id}`).then(r => r.data),
api.get<ShopPOResponse>(`/shop-pos/${id}`).then(r => r.data),
create: (body: SaleBody) =>
api.post<SaleResponse>('/sales', body).then(r => r.data),
create: (body: CreateShopPOBody) =>
api.post<ShopPOResponse>('/shop-pos', body).then(r => r.data),
updateItems: (id: string, body: UpdateShopPOItemsBody) =>
api.put<ShopPOResponse>(`/shop-pos/${id}/items`, body).then(r => r.data),
confirm: (id: string) =>
api.post<ShopPOResponse>(`/shop-pos/${id}/confirm`).then(r => r.data),
confirmAndCreateInvoice: (id: string) =>
api.post<ShopPOResponse>(`/shop-pos/${id}/confirm-and-create-invoice`).then(r => r.data),
cancel: (id: string) =>
api.patch<SaleResponse>(`/sales/${id}/cancel`).then(r => r.data),
api.post<ShopPOResponse>(`/shop-pos/${id}/cancel`).then(r => r.data),
}
// ─── Orders ───────────────────────────────────────────────────────────────────
// GET /orders takes no query params and returns a plain array — no server-side
// filtering/pagination exists on the backend, unlike Sales/Payments.
// ─── Invoices ─────────────────────────────────────────────────────────────────
export const orders = {
list: () =>
api.get<OrderResponse[]>('/orders').then(r => r.data),
export interface InvoiceListParams extends PageParams {
agentId?: string
routeId?: string
shopId?: string
status?: InvoiceStatus
}
export const invoices = {
list: (params?: InvoiceListParams) =>
api.get<PaginatedResponse<InvoiceResponse>>('/invoices', { params }).then(r => r.data),
get: (id: string) =>
api.get<OrderResponse>(`/orders/${id}`).then(r => r.data),
api.get<InvoiceResponse>(`/invoices/${id}`).then(r => r.data),
update: (id: string, status: OrderStatus) =>
api.put<OrderResponse>(`/orders/${id}`, { status }).then(r => r.data),
print: (id: string) =>
api.post<InvoiceResponse>(`/invoices/${id}/print`).then(r => r.data),
delete: (id: string) =>
api.delete(`/orders/${id}`),
finalize: (id: string) =>
api.post<InvoiceResponse>(`/invoices/${id}/finalize`).then(r => r.data),
}
// ─── Payments ─────────────────────────────────────────────────────────────────
export interface PaymentBody {
saleId: string
amount: number
method: PaymentMethod
// TEMPORARY: required while auth doesn't resolve a logged-in user server-side — see testbug.md #28.
collectedByUserId: string
}
export interface PaymentListParams extends PageParams {
storeId?: string
saleId?: string
method?: PaymentMethod
from?: string
to?: string
collectedByUserId?: string | null
}
export const payments = {
list: (params?: PaymentListParams) =>
api.get<PaginatedResponse<PaymentResponse>>('/payments', { params }).then(r => r.data),
listByInvoice: (invoiceId: string) =>
api.get<PaymentResponse[]>(`/invoices/${invoiceId}/payments`).then(r => r.data),
get: (id: string) =>
api.get<PaymentResponse>(`/payments/${id}`).then(r => r.data),
create: (body: PaymentBody) =>
api.post<PaymentResponse>('/payments', body).then(r => r.data),
create: (invoiceId: string, body: PaymentBody) =>
api.post<PaymentResponse>(`/invoices/${invoiceId}/payments`, body).then(r => r.data),
}