Fosi Fossil Service
Small teams. Open source. Built on Fossil.

IaC / SwiftFlow

High-performance WMS with wave picking, smart routing, and real-time inventory visibility.

Public | Owner: IaC | Default branch: trunk | Last update: 2026-10-23 14:37

Artifact 9767c415eb59ac7a028323954d4e5d52a5482f6d0d8a32ed6bb75bea7e1d2e4d:


import clock
import database.{type Conn, type DbError, type Database}
import gleam/int
import gleam/list
import gleam/option.{None, Some}
import gleam/result
import gleam/string
import location.{type Location, type LocationCode, Location}
import repo

pub const max_locations = 5000

pub type Outcome {
  Outcome(created: List(Location), existing: List(String))
}

pub type PlanError {
  PlanRejected(String)
  PlanMissingWarehouse
  PlanDb(DbError)
}

/// commit 为 False 时只预览,不写库。
pub fn run(db: Database, plan: Plan, commit: Bool) -> Result(Outcome, PlanError) {
  case expand(plan) {
    Error(message) -> Error(PlanRejected(message))
    Ok(codes) -> {
      let now = clock.now_local()
      case
        database.transaction(db, fn(conn) {
          apply(conn, plan.warehouse_id, codes, commit, now)
        })
      {
        Error(err) -> Error(PlanDb(err))
        Ok(Error(err)) -> Error(err)
        Ok(Ok(outcome)) -> Ok(outcome)
      }
    }
  }
}

fn apply(
  conn: Conn,
  warehouse_id: Int,
  codes: List(LocationCode),
  commit: Bool,
  now: String,
) -> Result(Result(Outcome, PlanError), DbError) {
  use warehouse <- result.try(repo.get_warehouse(conn, warehouse_id))
  case warehouse {
    None -> Ok(Error(PlanMissingWarehouse))
    Some(row) ->
      case row.operational {
        False -> Ok(Error(PlanRejected("该仓库只作显示,不能建立库存库位")))
        True -> {
          use pairs <- result.try(list.try_map(codes, fn(code) {
            use found <- result.try(repo.find_location(conn, warehouse_id, code.code))
            Ok(#(code, found))
          }))
          let existing =
            list.filter_map(pairs, fn(pair) {
              case pair.1 {
                Some(_) -> Ok({ pair.0 }.code)
                None -> Error(Nil)
              }
            })
          let created =
            list.filter_map(pairs, fn(pair) {
              case pair.1 {
                Some(_) -> Error(Nil)
                None -> {
                  let code = pair.0
                  Ok(Location(
                    warehouse_id: warehouse_id,
                    area: code.area,
                    aisle: code.aisle,
                    level: code.level,
                    bin: code.bin,
                    code: code.code,
                    barcode: location.barcode(row.warehouse.code, code.compact),
                    enabled: True,
                    created_at: now,
                    updated_at: now,
                  ))
                }
              }
            })
          use _ <- result.try(case commit {
            False -> Ok(Nil)
            True ->
              list.try_fold(created, Nil, fn(_, item) { repo.insert_location(conn, item) })
          })
          Ok(Ok(Outcome(created: created, existing: existing)))
        }
      }
  }
}

pub type Range {
  Range(from: Int, to: Int, width: Int)
}

pub type Plan {
  Plan(
    warehouse_id: Int,
    areas: List(String),
    aisle: Range,
    level: Range,
    bin: Range,
  )
}

pub fn expand(plan: Plan) -> Result(List(LocationCode), String) {
  use aisles <- result_try(numbers("货架", plan.aisle))
  use levels <- result_try(numbers("层", plan.level))
  use bins <- result_try(numbers("位", plan.bin))
  let areas =
    plan.areas
    |> list.map(fn(area) { string.uppercase(string.trim(area)) })
    |> list.unique
  case areas {
    [] -> Error("至少要有一个库区")
    _ -> {
      let count =
        list.length(areas)
        * list.length(aisles)
        * list.length(levels)
        * list.length(bins)
      case count > max_locations {
        True ->
          Error(
            "一次最多规划 "
            <> int.to_string(max_locations)
            <> " 个库位,本次是 "
            <> int.to_string(count)
            <> " 个",
          )
        False ->
          list.try_map(combinations(areas, aisles, levels, bins), fn(parts) {
            let #(area, aisle, level, bin) = parts
            location.build(area, aisle, level, bin)
          })
      }
    }
  }
}

fn combinations(
  areas: List(String),
  aisles: List(String),
  levels: List(String),
  bins: List(String),
) -> List(#(String, String, String, String)) {
  use area <- list.flat_map(areas)
  use aisle <- list.flat_map(aisles)
  use level <- list.flat_map(levels)
  use bin <- list.map(bins)
  #(area, aisle, level, bin)
}

fn numbers(label: String, range: Range) -> Result(List(String), String) {
  case range.width < 1 || range.width > 8 {
    True -> Error(label <> "编号位数应为 1 到 8")
    False ->
      case
        range.from < 0
        || range.to < range.from
        || range.to - range.from >= max_locations
      {
        True -> Error(label <> "的起止编号无效")
        False -> {
          let values =
            int.range(from: range.to, to: range.from - 1, with: [], run: fn(acc, value) {
              [value, ..acc]
            })
            |> list.map(fn(value) { pad(int.to_string(value), range.width) })
          case list.any(values, fn(value) { string.length(value) > range.width }) {
            True -> Error(label <> "编号超过了设定的位数")
            False -> Ok(values)
          }
        }
      }
  }
}

fn pad(value: String, width: Int) -> String {
  case string.length(value) >= width {
    True -> value
    False -> pad("0" <> value, width)
  }
}

fn result_try(result: Result(a, b), next: fn(a) -> Result(c, b)) -> Result(c, b) {
  case result {
    Ok(value) -> next(value)
    Error(err) -> Error(err)
  }
}

Clone

You can clone this repository with the following command:

fossil clone https://fosi.dev/IaC/SwiftFlow

Contribute

The best way to contribute is through e-mail. See the project documentation for more details.