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 fc2f65b2576927fb077adf42de7c77c5c6dd6ae05031183f7ce16fc508f8766c:


import gleam/dynamic/decode
import gleam/json
import gleam/option.{type Option, None, Some}
import gleam/result
import gleam/string
import ql361api/client.{type Client}
import ql361api/json_codec
import ql361api/models.{
  type ApiError, type Page, type SalesOrder, type SalesOrderSummary, ApiFailure,
  AuthExpired, HttpError, JsonError, NetworkError, Page, SalesOrder, SalesOrderItem,
  SalesOrderSummary, UrlParseError,
}

pub const query_orders_method = "cc.erp.bll.bill.billbllmanager.getdatalist"
pub const get_full_data_method = "cc.erp.bll.bill.billbllmanager.getfulldata"
pub const page_size = 100

pub fn fetch_sales_orders(
  client client: Client,
  begin_date begin_date: String,
  end_date end_date: String,
  status status: Option(Int),
) -> Result(List(SalesOrderSummary), ApiError) {
  use page <- result.try(fetch_sales_order_page(
    client,
    begin_date,
    end_date,
    status,
    0,
    page_size,
  ))
  Ok(page.items)
}

pub fn fetch_sales_order_page(
  client client: Client,
  begin_date begin_date: String,
  end_date end_date: String,
  status status: Option(Int),
  page page: Int,
  page_size page_size: Int,
) -> Result(Page(SalesOrderSummary), ApiError) {
  let condition = [
    #("begindate", json.string(begin_date)),
    #("enddate", json.string(end_date)),
    #("billtype", json.int(604)),
  ]
  let condition = case status {
    Some(value) -> [#("status", json.int(value)), ..condition]
    None -> condition
  }

  let payload =
    json.object([
      #(
        "reportparam",
        json.object([
          #("orders", json.string("")),
          #("parid", json.int(0)),
          #("pagetype", json.string("erp")),
          #("displaytype", json.string("tree")),
          #("pagerow", json.int(page_size)),
          #("pagenumber", json.int(page)),
        ]),
      ),
      #("conditionparam", json.object(condition)),
    ])
    |> json.to_string

  use raw_json <- result.try(
    client.post_rpc(client, query_orders_method, "erp", payload),
  )
  parse_orders_body(raw_json)
}

pub fn fetch_pending_picking_orders(
  client client: Client,
  begin_date begin_date: String,
  end_date end_date: String,
) -> Result(List(SalesOrderSummary), ApiError) {
  fetch_sales_orders(client, begin_date, end_date, Some(20))
}

pub fn fetch_order_detail(
  client client: Client,
  bill_id bill_id: Int,
) -> Result(SalesOrder, ApiError) {
  let payload =
    json.object([
      #("billid", json.int(bill_id)),
      #("billtype", json.int(604)),
    ])
    |> json.to_string

  use raw_json <- result.try(
    client.post_rpc(client, get_full_data_method, "erp", payload),
  )
  parse_order_detail_body(raw_json)
}

pub fn parse_orders_body(
  body: String,
) -> Result(Page(SalesOrderSummary), ApiError) {
  let summary_decoder = {
    use bill_id <- decode.field("billid", json_codec.int())
    use bill_code <- decode.optional_field("billcode", "", decode.string)
    use customer_name <- decode.optional_field("dfullname", "", decode.string)
    use contact_person <- decode.optional_field("contactor", "", decode.string)
    use phone <- decode.optional_field("phone", "", decode.string)
    use warehouse_name <- decode.optional_field("soutfullname", "", decode.string)
    use warehouse_id <- decode.optional_field("soutid", 0, json_codec.int())
    use bill_date <- decode.optional_field("billdate", "", decode.string)
    use order_status <- decode.optional_field("status", 0, json_codec.int())
    use status_name <- decode.optional_field("statusname", "", decode.string)
    use total_qty <- decode.optional_field("qty", 0.0, json_codec.number())
    use total_money <- decode.optional_field("totalmoney", 0.0, json_codec.number())
    use remark <- decode.optional_field("remark", "", decode.string)
    decode.success(SalesOrderSummary(
      bill_id: bill_id,
      bill_code: bill_code,
      customer_name: customer_name,
      contact_person: contact_person,
      phone: phone,
      warehouse_name: warehouse_name,
      warehouse_id: warehouse_id,
      bill_date: bill_date,
      status: order_status,
      status_name: status_name,
      total_qty: total_qty,
      total_money: total_money,
      remark: remark,
    ))
  }

  let data_decoder = {
    use items <- decode.field("datasource", decode.list(of: summary_decoder))
    use total <- decode.optional_field("rowcount", 0, json_codec.int())
    decode.success(Page(items: items, total: total))
  }

  let wrapper_decoder = {
    use data <- decode.field("data", data_decoder)
    decode.success(data)
  }

  case json.parse(body, wrapper_decoder) {
    Ok(page) -> Ok(page)
    Error(_) -> Error(JsonError("销售订单列表响应无法解析"))
  }
}

pub fn parse_order_detail_body(body: String) -> Result(SalesOrder, ApiError) {
  let item_decoder = {
    use bill_detail_id <- decode.optional_field(
      "billdetailid",
      0,
      json_codec.int(),
    )
    use sku_id <- decode.optional_field("skuid", 0, json_codec.int())
    use goods_code <- decode.optional_field("gcode", "", decode.string)
    use goods_name <- decode.optional_field("gfullname", "", decode.string)
    use barcode <- decode.optional_field("barcode", "", decode.string)
    use spec <- decode.optional_field("spec", "", decode.string)
    use package_qty <- decode.optional_field("mqty", 0.0, json_codec.number())
    use package_unit <- decode.optional_field("mufullname", "", decode.string)
    use base_qty <- decode.optional_field("qty", 0.0, json_codec.number())
    use base_unit <- decode.optional_field("bufullname", "", decode.string)
    use price <- decode.optional_field("price", 0.0, json_codec.number())
    use total_money <- decode.optional_field("totalmoney", 0.0, json_codec.number())
    use position_code <- decode.optional_field("gpcode", "", decode.string)
    use area_code <- decode.optional_field("sacode", "", decode.string)
    decode.success(SalesOrderItem(
      bill_detail_id: bill_detail_id,
      sku_id: sku_id,
      goods_code: goods_code,
      goods_name: goods_name,
      barcode: barcode,
      spec: spec,
      package_qty: package_qty,
      package_unit: package_unit,
      base_qty: base_qty,
      base_unit: base_unit,
      price: price,
      total_money: total_money,
      position_code: position_code,
      area_code: area_code,
    ))
  }

  let body0_decoder = {
    use datasource <- decode.field("datasource", decode.list(of: item_decoder))
    decode.success(datasource)
  }

  let header_decoder = {
    use bill_id <- decode.field("billid", json_codec.int())
    use bill_code <- decode.optional_field("billcode", "", decode.string)
    use customer_name <- decode.optional_field("dfullname", "", decode.string)
    use contact_person <- decode.optional_field("contactor", "", decode.string)
    use phone <- decode.optional_field("phone", "", decode.string)
    use warehouse_name <- decode.optional_field("sfullname", "", decode.string)
    use warehouse_id <- decode.optional_field("soutid", 0, json_codec.int())
    use bill_date <- decode.optional_field("billdate", "", decode.string)
    use order_status <- decode.optional_field("status", 0, json_codec.int())
    use status_name <- decode.optional_field("statusname", "", decode.string)
    use total_qty <- decode.optional_field("qty", 0.0, json_codec.number())
    use total_money <- decode.optional_field("totalmoney", 0.0, json_codec.number())
    use remark <- decode.optional_field("remark", "", decode.string)
    decode.success(#(
      bill_id,
      bill_code,
      customer_name,
      contact_person,
      phone,
      warehouse_name,
      warehouse_id,
      bill_date,
      order_status,
      status_name,
      total_qty,
      total_money,
      remark,
    ))
  }

  let data_decoder = {
    use header <- decode.field("header", header_decoder)
    use body0 <- decode.field("body0", body0_decoder)
    decode.success(SalesOrder(
      bill_id: header.0,
      bill_code: header.1,
      customer_name: header.2,
      contact_person: header.3,
      phone: header.4,
      warehouse_name: header.5,
      warehouse_id: header.6,
      bill_date: header.7,
      status: header.8,
      status_name: header.9,
      total_qty: header.10,
      total_money: header.11,
      remark: header.12,
      items: body0,
    ))
  }

  let wrapper_decoder = {
    use data <- decode.field("data", data_decoder)
    decode.success(data)
  }

  case json.parse(body, wrapper_decoder) {
    Ok(order) -> Ok(order)
    Error(_) -> Error(JsonError("销售订单明细响应无法解析"))
  }
}

pub fn describe_error(error: ApiError) -> String {
  case error {
    HttpError(status, _) -> "QL361 HTTP " <> string.inspect(status)
    NetworkError(message) -> "QL361 网络错误: " <> message
    JsonError(message) -> message
    AuthExpired(message) -> "QL361 会话失效: " <> message
    ApiFailure(message) -> message
    UrlParseError -> "QL361 地址无法解析"
  }
}

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.