汇总已落盘的借款本金与还款、兼容历史编号的周期化编号、付款责任与回单核验、报销进度和排序、PWA/采购事务、错误原因与日期校验及ERP退役。 统一共享预算池锁序和锁后判断,恢复差旅原预算选择,补原因码固定夹具及反向完整性检查。 纳入配套迁移、测试、受管QA、分层验收合同、规约及任务记录;原始证据留在ignored artifacts。 关联任务:09-17-company-loan-principal-repayment、09-17-erp-exclusive-retirement、09-18-reimbursement-periodic-numbering、09-08-reimbursement-payment-closeout、09-18-local-concurrency-product-fixes、09-19-budget-pool-concurrency、09-05-error-explainability-payment-readiness。 预算补审由Claude定向验证2文件928项通过,原4446/28及r6结果仍仅对应各自历史输入;本轮未重跑测试。 完整新候选门禁、R1真实入口接入与12项公网义务仍开放。生产编号未启用、差旅仍冻结;不推送、不部署,不宣称发布资格。
371 行
16 KiB
Bash
可执行文件
371 行
16 KiB
Bash
可执行文件
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
BASE_URL="${BASE_URL:-http://127.0.0.1:3000}"
|
|
DEVICE_FINGERPRINT="${DEVICE_FINGERPRINT:-regression-assets}"
|
|
EMPLOYEE_IDENTIFIER="${EMPLOYEE_IDENTIFIER:-employee@hyinda.com}"
|
|
EMPLOYEE_PASSWORD="${EMPLOYEE_PASSWORD:-EmployeePassword1234}"
|
|
FINANCE_MGR_IDENTIFIER="${FINANCE_MGR_IDENTIFIER:-finance_mgr@hyinda.com}"
|
|
FINANCE_MGR_PASSWORD="${FINANCE_MGR_PASSWORD:-FinanceMgrPassword1234}"
|
|
ADMINISTRATION_IDENTIFIER="${ADMINISTRATION_IDENTIFIER:-administration@hyinda.com}"
|
|
ADMINISTRATION_PASSWORD="${ADMINISTRATION_PASSWORD:-AdministrationPassword1234}"
|
|
|
|
if ! command -v curl >/dev/null 2>&1; then
|
|
echo "[ERROR] curl not found" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|
echo "[ERROR] jq not found" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT_DIR"
|
|
|
|
# shellcheck source=scripts/lib/load-env-defaults.sh
|
|
source "$ROOT_DIR/scripts/lib/load-env-defaults.sh"
|
|
load_env_defaults ".env"
|
|
|
|
if [[ -z "${DATABASE_URL:-}" ]]; then
|
|
echo "[ERROR] DATABASE_URL is required (export it or provide server/.env)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
TMP_DIR="$(mktemp -d)"
|
|
cleanup() {
|
|
rm -rf "$TMP_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
assert_status() {
|
|
local actual="$1"
|
|
local expected="$2"
|
|
local name="$3"
|
|
local body_file="${4:-}"
|
|
|
|
if [[ "$actual" != "$expected" ]]; then
|
|
echo "[ERROR] $name expected HTTP $expected but got $actual" >&2
|
|
[[ -n "$body_file" && -f "$body_file" ]] && cat "$body_file" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
assert_jq() {
|
|
local file_path="$1"
|
|
local jq_expr="$2"
|
|
local name="$3"
|
|
if ! jq -e "$jq_expr" "$file_path" >/dev/null; then
|
|
echo "[ERROR] $name assertion failed: $jq_expr" >&2
|
|
cat "$file_path" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
login_user() {
|
|
local identifier="$1"
|
|
local password="$2"
|
|
local out_file="$3"
|
|
|
|
local status
|
|
status="$(curl -sS -o "$out_file" -w "%{http_code}" \
|
|
-X POST "$BASE_URL/api/auth/login" \
|
|
-H "content-type: application/json" \
|
|
-H "x-device-fingerprint: $DEVICE_FINGERPRINT" \
|
|
-d "{\"identifier\":\"$identifier\",\"password\":\"$password\"}")"
|
|
assert_status "$status" "200" "login $identifier" "$out_file"
|
|
jq -er '.data.accessToken' "$out_file"
|
|
}
|
|
|
|
api_json_auth() {
|
|
local method="$1"
|
|
local url="$2"
|
|
local token="$3"
|
|
local out_file="$4"
|
|
local body="${5:-}"
|
|
|
|
if [[ -n "$body" ]]; then
|
|
curl -sS -o "$out_file" -w "%{http_code}" \
|
|
-X "$method" "$url" \
|
|
-H "authorization: Bearer $token" \
|
|
-H "content-type: application/json" \
|
|
-d "$body"
|
|
else
|
|
curl -sS -o "$out_file" -w "%{http_code}" \
|
|
-X "$method" "$url" \
|
|
-H "authorization: Bearer $token"
|
|
fi
|
|
}
|
|
|
|
resolve_fixture_ids() {
|
|
DATABASE_URL="$DATABASE_URL" node - <<'NODE'
|
|
const { createPrismaClient } = require('./src/prismaClient');
|
|
|
|
(async () => {
|
|
const { prisma, pool } = createPrismaClient();
|
|
await prisma.$connect();
|
|
try {
|
|
const employee = await prisma.employee.findUnique({
|
|
where: { email: process.env.SEED_EMPLOYEE_EMAIL ?? '[email protected]' },
|
|
select: { id: true, legal_entity_id: true },
|
|
});
|
|
const financeMgr = await prisma.employee.findUnique({
|
|
where: { email: process.env.SEED_FINANCE_MGR_EMAIL ?? '[email protected]' },
|
|
select: { id: true },
|
|
});
|
|
const administration = await prisma.employee.findUnique({
|
|
where: { email: process.env.SEED_ADMINISTRATION_EMAIL ?? '[email protected]' },
|
|
select: { id: true },
|
|
});
|
|
const costCenter = await prisma.costCenter.findFirst({
|
|
where: {
|
|
legal_entity_id: employee.legal_entity_id,
|
|
enabled: true,
|
|
archived_at: null,
|
|
},
|
|
orderBy: { created_at: 'asc' },
|
|
select: { id: true },
|
|
});
|
|
const approvedPurchase = await prisma.purchaseRequest.findFirst({
|
|
where: { status: 'approved' },
|
|
orderBy: { updated_at: 'desc' },
|
|
select: { id: true },
|
|
});
|
|
|
|
process.stdout.write([
|
|
employee.id,
|
|
financeMgr.id,
|
|
administration.id,
|
|
employee.legal_entity_id ?? '',
|
|
costCenter?.id ?? '',
|
|
approvedPurchase?.id ?? '',
|
|
].join('\n'));
|
|
} finally {
|
|
await prisma.$disconnect();
|
|
await pool.end();
|
|
}
|
|
})().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|
|
NODE
|
|
}
|
|
|
|
assert_asset_state() {
|
|
local asset_id="$1"
|
|
local category_id="$2"
|
|
local finance_mgr_id="$3"
|
|
local inventory_check_id="$4"
|
|
|
|
DATABASE_URL="$DATABASE_URL" node - "$asset_id" "$category_id" "$finance_mgr_id" "$inventory_check_id" <<'NODE'
|
|
const [assetId, categoryId, financeMgrId, inventoryCheckId] = process.argv.slice(2);
|
|
const { createPrismaClient } = require('./src/prismaClient');
|
|
|
|
(async () => {
|
|
const { prisma, pool } = createPrismaClient();
|
|
await prisma.$connect();
|
|
try {
|
|
const category = await prisma.assetCategory.findUnique({
|
|
where: { id: categoryId },
|
|
select: { name: true },
|
|
});
|
|
const asset = await prisma.assetCard.findUnique({
|
|
where: { id: assetId },
|
|
select: {
|
|
asset_no: true,
|
|
status: true,
|
|
assignee_employee_id: true,
|
|
location: true,
|
|
},
|
|
});
|
|
const assignments = await prisma.assetAssignment.findMany({
|
|
where: { asset_id: assetId },
|
|
orderBy: { assigned_at: 'asc' },
|
|
select: { employee_id: true, returned_at: true },
|
|
});
|
|
const transfers = await prisma.assetTransfer.findMany({
|
|
where: { asset_id: assetId },
|
|
select: { to_employee_id: true, to_location: true },
|
|
});
|
|
const inventoryCheck = await prisma.inventoryCheck.findUnique({
|
|
where: { id: inventoryCheckId },
|
|
select: {
|
|
status: true,
|
|
items: {
|
|
select: { asset_id: true, result: true },
|
|
},
|
|
},
|
|
});
|
|
const auditActions = await prisma.auditLog.findMany({
|
|
where: {
|
|
target_id: { in: [assetId, inventoryCheckId] },
|
|
action: {
|
|
in: [
|
|
'ASSET_ASSIGNED',
|
|
'ASSET_RETURNED',
|
|
'ASSET_TRANSFERRED',
|
|
'INVENTORY_CHECK_CREATED',
|
|
'INVENTORY_CHECK_STARTED',
|
|
'INVENTORY_ITEM_RECORDED',
|
|
'INVENTORY_CHECK_COMPLETED',
|
|
],
|
|
},
|
|
},
|
|
select: { action: true },
|
|
});
|
|
|
|
const openAssignments = assignments.filter((item) => item.returned_at == null);
|
|
const actionSet = new Set(auditActions.map((item) => item.action));
|
|
|
|
if (!category || category.name !== '回归资产分类-已更新') {
|
|
console.error('[ERROR] asset category state mismatch', category);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!asset || !/^FA-\d{4}-\d{6,}$/.test(asset.asset_no) || asset.status !== 'in_use' || asset.assignee_employee_id !== financeMgrId || asset.location !== '深圳办公室 B-02') {
|
|
console.error('[ERROR] asset card state mismatch', asset);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (assignments.length < 3 || openAssignments.length !== 1 || openAssignments[0].employee_id !== financeMgrId) {
|
|
console.error('[ERROR] asset assignments state mismatch', assignments);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!transfers.length || transfers[0].to_employee_id !== financeMgrId || transfers[0].to_location !== '深圳办公室 B-02') {
|
|
console.error('[ERROR] asset transfer state mismatch', transfers);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (!inventoryCheck || inventoryCheck.status !== 'completed' || inventoryCheck.items.length !== 1 || inventoryCheck.items[0].asset_id !== assetId || inventoryCheck.items[0].result !== 'matched') {
|
|
console.error('[ERROR] inventory check state mismatch', inventoryCheck);
|
|
process.exit(1);
|
|
}
|
|
|
|
const requiredActions = [
|
|
'ASSET_ASSIGNED',
|
|
'ASSET_RETURNED',
|
|
'ASSET_TRANSFERRED',
|
|
'INVENTORY_CHECK_CREATED',
|
|
'INVENTORY_CHECK_STARTED',
|
|
'INVENTORY_ITEM_RECORDED',
|
|
'INVENTORY_CHECK_COMPLETED',
|
|
];
|
|
for (const action of requiredActions) {
|
|
if (!actionSet.has(action)) {
|
|
console.error('[ERROR] missing audit action', action, auditActions);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
} finally {
|
|
await prisma.$disconnect();
|
|
await pool.end();
|
|
}
|
|
})().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|
|
NODE
|
|
}
|
|
|
|
echo "[1/10] login employee finance manager and administration"
|
|
EMPLOYEE_TOKEN="$(login_user "$EMPLOYEE_IDENTIFIER" "$EMPLOYEE_PASSWORD" "$TMP_DIR/employee-login.json")"
|
|
FINANCE_MGR_TOKEN="$(login_user "$FINANCE_MGR_IDENTIFIER" "$FINANCE_MGR_PASSWORD" "$TMP_DIR/finance-mgr-login.json")"
|
|
ADMINISTRATION_TOKEN="$(login_user "$ADMINISTRATION_IDENTIFIER" "$ADMINISTRATION_PASSWORD" "$TMP_DIR/administration-login.json")"
|
|
mapfile -t FIXTURE_IDS < <(resolve_fixture_ids)
|
|
EMPLOYEE_ID="${FIXTURE_IDS[0]}"
|
|
FINANCE_MGR_ID="${FIXTURE_IDS[1]}"
|
|
ADMINISTRATION_ID="${FIXTURE_IDS[2]}"
|
|
LEGAL_ENTITY_ID="${FIXTURE_IDS[3]}"
|
|
COST_CENTER_ID="${FIXTURE_IDS[4]}"
|
|
APPROVED_PURCHASE_ID="${FIXTURE_IDS[5]}"
|
|
|
|
echo "[2/10] finance manager create and update asset category"
|
|
CATEGORY_CREATE_BODY="$TMP_DIR/category-create.json"
|
|
CATEGORY_CREATE_STATUS="$(api_json_auth POST "$BASE_URL/api/asset-categories" "$FINANCE_MGR_TOKEN" "$CATEGORY_CREATE_BODY" '{"code":"REG_ASSET_CAT_'"$(date +%s)'"'","name":"回归资产分类","enabled":true}')"
|
|
assert_status "$CATEGORY_CREATE_STATUS" "200" "create asset category" "$CATEGORY_CREATE_BODY"
|
|
CATEGORY_ID="$(jq -er '.data.assetCategory.id' "$CATEGORY_CREATE_BODY")"
|
|
CATEGORY_PATCH_BODY="$TMP_DIR/category-patch.json"
|
|
CATEGORY_PATCH_STATUS="$(api_json_auth PATCH "$BASE_URL/api/asset-categories/$CATEGORY_ID" "$FINANCE_MGR_TOKEN" "$CATEGORY_PATCH_BODY" '{"name":"回归资产分类-已更新"}')"
|
|
assert_status "$CATEGORY_PATCH_STATUS" "200" "patch asset category" "$CATEGORY_PATCH_BODY"
|
|
|
|
echo "[3/10] finance manager creates asset card"
|
|
ASSET_CREATE_PAYLOAD="$(jq -n \
|
|
--arg name "回归资产卡片 $(date +%s)" \
|
|
--arg categoryId "$CATEGORY_ID" \
|
|
--arg legalEntityId "$LEGAL_ENTITY_ID" \
|
|
--arg costCenterId "$COST_CENTER_ID" \
|
|
--arg purchaseRequestId "$APPROVED_PURCHASE_ID" \
|
|
'{name:$name, categoryId:$categoryId, legalEntityId:$legalEntityId, costCenterId:$costCenterId, purchaseRequestId:($purchaseRequestId|select(length>0)), location:"深圳办公室 A-01", status:"in_stock"}')"
|
|
ASSET_CREATE_BODY="$TMP_DIR/asset-create.json"
|
|
ASSET_CREATE_STATUS="$(api_json_auth POST "$BASE_URL/api/assets" "$FINANCE_MGR_TOKEN" "$ASSET_CREATE_BODY" "$ASSET_CREATE_PAYLOAD")"
|
|
assert_status "$ASSET_CREATE_STATUS" "200" "create asset card" "$ASSET_CREATE_BODY"
|
|
ASSET_ID="$(jq -er '.data.asset.id' "$ASSET_CREATE_BODY")"
|
|
|
|
|
|
echo "[4/10] direct assignee patch blocked to enforce controlled transfer"
|
|
ASSET_PATCH_BODY="$TMP_DIR/asset-patch-invalid.json"
|
|
ASSET_PATCH_STATUS="$(api_json_auth PATCH "$BASE_URL/api/assets/$ASSET_ID" "$FINANCE_MGR_TOKEN" "$ASSET_PATCH_BODY" '{"assigneeEmployeeId":"'"$EMPLOYEE_ID"'"}')"
|
|
assert_status "$ASSET_PATCH_STATUS" "400" "patch asset assignee invalid" "$ASSET_PATCH_BODY"
|
|
assert_jq "$ASSET_PATCH_BODY" '.error.code == "VALID_001"' "patch asset assignee invalid code"
|
|
|
|
echo "[5/10] administration assign asset to employee"
|
|
ASSET_ASSIGN_BODY="$TMP_DIR/asset-assign.json"
|
|
ASSET_ASSIGN_STATUS="$(api_json_auth POST "$BASE_URL/api/assets/$ASSET_ID/assign" "$ADMINISTRATION_TOKEN" "$ASSET_ASSIGN_BODY" '{"employeeId":"'"$EMPLOYEE_ID"'","note":"首次领用"}')"
|
|
assert_status "$ASSET_ASSIGN_STATUS" "200" "assign asset" "$ASSET_ASSIGN_BODY"
|
|
assert_jq "$ASSET_ASSIGN_BODY" '.data.assigneeEmployeeId == "'"$EMPLOYEE_ID"'"' "assign asset assignee"
|
|
|
|
echo "[6/10] employee only sees own asset and cannot transfer"
|
|
EMPLOYEE_ASSETS_BODY="$TMP_DIR/employee-assets.json"
|
|
EMPLOYEE_ASSETS_STATUS="$(api_json_auth GET "$BASE_URL/api/assets?mine=0" "$EMPLOYEE_TOKEN" "$EMPLOYEE_ASSETS_BODY")"
|
|
assert_status "$EMPLOYEE_ASSETS_STATUS" "200" "employee assets mine forced" "$EMPLOYEE_ASSETS_BODY"
|
|
assert_jq "$EMPLOYEE_ASSETS_BODY" '.data.mine == true' "employee mine forced"
|
|
assert_jq "$EMPLOYEE_ASSETS_BODY" '.data.items[] | select(.id == "'"$ASSET_ID"'") | .assigneeEmployeeId == "'"$EMPLOYEE_ID"'"' "employee sees owned asset"
|
|
EMPLOYEE_TRANSFER_BODY="$TMP_DIR/employee-transfer.json"
|
|
EMPLOYEE_TRANSFER_STATUS="$(api_json_auth POST "$BASE_URL/api/assets/$ASSET_ID/transfer" "$EMPLOYEE_TOKEN" "$EMPLOYEE_TRANSFER_BODY" '{"toLocation":"深圳办公室 B-01"}')"
|
|
assert_status "$EMPLOYEE_TRANSFER_STATUS" "403" "employee transfer forbidden" "$EMPLOYEE_TRANSFER_BODY"
|
|
assert_jq "$EMPLOYEE_TRANSFER_BODY" '.error.code == "PERM_001"' "employee transfer forbidden code"
|
|
|
|
echo "[7/10] administration return then assign asset again"
|
|
ASSET_RETURN_BODY="$TMP_DIR/asset-return.json"
|
|
ASSET_RETURN_STATUS="$(api_json_auth POST "$BASE_URL/api/assets/$ASSET_ID/return" "$ADMINISTRATION_TOKEN" "$ASSET_RETURN_BODY" '{"note":"归还入库"}')"
|
|
assert_status "$ASSET_RETURN_STATUS" "200" "return asset" "$ASSET_RETURN_BODY"
|
|
assert_jq "$ASSET_RETURN_BODY" '.data.assigneeEmployeeId == null' "return clears assignee"
|
|
ASSET_REASSIGN_BODY="$TMP_DIR/asset-reassign.json"
|
|
ASSET_REASSIGN_STATUS="$(api_json_auth POST "$BASE_URL/api/assets/$ASSET_ID/assign" "$ADMINISTRATION_TOKEN" "$ASSET_REASSIGN_BODY" '{"employeeId":"'"$EMPLOYEE_ID"'","note":"再次领用"}')"
|
|
assert_status "$ASSET_REASSIGN_STATUS" "200" "reassign asset" "$ASSET_REASSIGN_BODY"
|
|
|
|
echo "[8/10] administration transfers asset to finance manager and new location"
|
|
ASSET_TRANSFER_BODY="$TMP_DIR/asset-transfer.json"
|
|
ASSET_TRANSFER_STATUS="$(api_json_auth POST "$BASE_URL/api/assets/$ASSET_ID/transfer" "$ADMINISTRATION_TOKEN" "$ASSET_TRANSFER_BODY" '{"toEmployeeId":"'"$FINANCE_MGR_ID"'","toLocation":"深圳办公室 B-02","note":"部门间调拨"}')"
|
|
assert_status "$ASSET_TRANSFER_STATUS" "200" "transfer asset" "$ASSET_TRANSFER_BODY"
|
|
assert_jq "$ASSET_TRANSFER_BODY" '.data.assigneeEmployeeId == "'"$FINANCE_MGR_ID"'"' "transfer asset assignee"
|
|
assert_jq "$ASSET_TRANSFER_BODY" '.data.location == "深圳办公室 B-02"' "transfer asset location"
|
|
|
|
echo "[9/10] finance manager creates completes inventory check; employee forbidden"
|
|
INVENTORY_CREATE_BODY="$TMP_DIR/inventory-create.json"
|
|
INVENTORY_CREATE_STATUS="$(api_json_auth POST "$BASE_URL/api/inventory-checks" "$FINANCE_MGR_TOKEN" "$INVENTORY_CREATE_BODY" '{"scopeType":"cost_center","scopeRefId":"'"$COST_CENTER_ID"'"}')"
|
|
assert_status "$INVENTORY_CREATE_STATUS" "200" "create inventory check" "$INVENTORY_CREATE_BODY"
|
|
INVENTORY_CHECK_ID="$(jq -er '.data.inventoryCheck.id' "$INVENTORY_CREATE_BODY")"
|
|
INVENTORY_START_BODY="$TMP_DIR/inventory-start.json"
|
|
INVENTORY_START_STATUS="$(api_json_auth POST "$BASE_URL/api/inventory-checks/$INVENTORY_CHECK_ID/start" "$FINANCE_MGR_TOKEN" "$INVENTORY_START_BODY")"
|
|
assert_status "$INVENTORY_START_STATUS" "200" "start inventory check" "$INVENTORY_START_BODY"
|
|
INVENTORY_ITEM_BODY="$TMP_DIR/inventory-item.json"
|
|
INVENTORY_ITEM_STATUS="$(api_json_auth POST "$BASE_URL/api/inventory-checks/$INVENTORY_CHECK_ID/items" "$FINANCE_MGR_TOKEN" "$INVENTORY_ITEM_BODY" '{"items":[{"assetId":"'"$ASSET_ID"'","result":"matched","note":"盘点一致"}]}')"
|
|
assert_status "$INVENTORY_ITEM_STATUS" "200" "record inventory item" "$INVENTORY_ITEM_BODY"
|
|
INVENTORY_COMPLETE_BODY="$TMP_DIR/inventory-complete.json"
|
|
INVENTORY_COMPLETE_STATUS="$(api_json_auth POST "$BASE_URL/api/inventory-checks/$INVENTORY_CHECK_ID/complete" "$FINANCE_MGR_TOKEN" "$INVENTORY_COMPLETE_BODY")"
|
|
assert_status "$INVENTORY_COMPLETE_STATUS" "200" "complete inventory check" "$INVENTORY_COMPLETE_BODY"
|
|
EMPLOYEE_INVENTORY_BODY="$TMP_DIR/employee-inventory.json"
|
|
EMPLOYEE_INVENTORY_STATUS="$(api_json_auth GET "$BASE_URL/api/inventory-checks" "$EMPLOYEE_TOKEN" "$EMPLOYEE_INVENTORY_BODY")"
|
|
assert_status "$EMPLOYEE_INVENTORY_STATUS" "403" "employee inventory forbidden" "$EMPLOYEE_INVENTORY_BODY"
|
|
assert_jq "$EMPLOYEE_INVENTORY_BODY" '.error.code == "PERM_001"' "employee inventory forbidden code"
|
|
|
|
echo "[10/10] verify asset movement inventory db state and audit logs"
|
|
assert_asset_state "$ASSET_ID" "$CATEGORY_ID" "$FINANCE_MGR_ID" "$INVENTORY_CHECK_ID"
|
|
|
|
echo "[OK] Asset regression passed"
|
|
echo "- assetCategoryId: $CATEGORY_ID"
|
|
echo "- assetId: $ASSET_ID"
|
|
echo "- inventoryCheckId: $INVENTORY_CHECK_ID"
|