修复产品需规保存与表格布局问题
This commit is contained in:
parent
867fc3beee
commit
b024b061e9
@ -88,6 +88,87 @@ from app.services.system_permissions import list_employee_options
|
|||||||
|
|
||||||
router = APIRouter(dependencies=[Depends(require_authenticated_user)])
|
router = APIRouter(dependencies=[Depends(require_authenticated_user)])
|
||||||
|
|
||||||
|
DEFAULT_PROCESS_CODE = "PROC-DEFAULT"
|
||||||
|
DEFAULT_PROCESS_NAME = "系统默认工序"
|
||||||
|
DEFAULT_WORK_CENTER_CODE = "WC-DEFAULT"
|
||||||
|
DEFAULT_WORK_CENTER_NAME = "系统默认工作中心"
|
||||||
|
DEFAULT_ORG_ROOT_CODE = "ORG_ROOT"
|
||||||
|
|
||||||
|
|
||||||
|
def _default_operation_department(db: Session) -> Department:
|
||||||
|
department = db.scalar(select(Department).where(Department.dept_code == DEFAULT_ORG_ROOT_CODE).limit(1))
|
||||||
|
if department:
|
||||||
|
return department
|
||||||
|
|
||||||
|
department = db.scalar(select(Department).order_by(Department.id.asc()).limit(1))
|
||||||
|
if department:
|
||||||
|
return department
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
department = Department(
|
||||||
|
dept_code=DEFAULT_ORG_ROOT_CODE,
|
||||||
|
dept_name="总公司",
|
||||||
|
parent_id=None,
|
||||||
|
org_node_type="COMPANY",
|
||||||
|
dept_type="ADMIN",
|
||||||
|
manager_name=None,
|
||||||
|
manager_employee_id=None,
|
||||||
|
status="ACTIVE",
|
||||||
|
sort_no=0,
|
||||||
|
remark="系统自动创建的默认组织根节点",
|
||||||
|
created_at=now,
|
||||||
|
updated_at=now,
|
||||||
|
)
|
||||||
|
db.add(department)
|
||||||
|
db.flush()
|
||||||
|
return department
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_default_operation_foundation(db: Session) -> None:
|
||||||
|
changed = False
|
||||||
|
now = datetime.now()
|
||||||
|
process_exists = db.scalar(select(Process.id).limit(1))
|
||||||
|
if not process_exists:
|
||||||
|
db.add(
|
||||||
|
Process(
|
||||||
|
process_code=DEFAULT_PROCESS_CODE,
|
||||||
|
process_name=DEFAULT_PROCESS_NAME,
|
||||||
|
process_type="INTERNAL",
|
||||||
|
is_report_required=1,
|
||||||
|
is_quality_gate=0,
|
||||||
|
status="ACTIVE",
|
||||||
|
remark="系统自动创建,用于产品需规快速建档时生成默认工艺路线",
|
||||||
|
created_at=now,
|
||||||
|
updated_at=now,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
work_center_exists = db.scalar(select(WorkCenter.id).limit(1))
|
||||||
|
if not work_center_exists:
|
||||||
|
department = _default_operation_department(db)
|
||||||
|
db.add(
|
||||||
|
WorkCenter(
|
||||||
|
center_code=DEFAULT_WORK_CENTER_CODE,
|
||||||
|
center_name=DEFAULT_WORK_CENTER_NAME,
|
||||||
|
dept_id=department.id,
|
||||||
|
center_type="INTERNAL",
|
||||||
|
shift_pattern="默认班次",
|
||||||
|
capacity_hours_per_day=Decimal("8.00"),
|
||||||
|
status="ACTIVE",
|
||||||
|
remark="系统自动创建,用于产品需规快速建档时生成默认工艺路线",
|
||||||
|
created_at=now,
|
||||||
|
updated_at=now,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
try:
|
||||||
|
db.commit()
|
||||||
|
except IntegrityError:
|
||||||
|
db.rollback()
|
||||||
|
|
||||||
MATERIAL_EXCEL_HEADERS = ["原材料编码", "原材料名称", "材质", "规格", "废料单价", "性能要求", "状态"]
|
MATERIAL_EXCEL_HEADERS = ["原材料编码", "原材料名称", "材质", "规格", "废料单价", "性能要求", "状态"]
|
||||||
MATERIAL_EXCEL_REQUIRED_HEADERS = ["原材料名称", "材质", "规格"]
|
MATERIAL_EXCEL_REQUIRED_HEADERS = ["原材料名称", "材质", "规格"]
|
||||||
MATERIAL_STATUS_LABELS = {
|
MATERIAL_STATUS_LABELS = {
|
||||||
@ -597,6 +678,7 @@ def list_work_centers(
|
|||||||
limit: int = Query(default=100, ge=1, le=500),
|
limit: int = Query(default=100, ge=1, le=500),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> list[WorkCenterRead]:
|
) -> list[WorkCenterRead]:
|
||||||
|
_ensure_default_operation_foundation(db)
|
||||||
stmt = (
|
stmt = (
|
||||||
select(
|
select(
|
||||||
WorkCenter.id.label("id"),
|
WorkCenter.id.label("id"),
|
||||||
@ -766,6 +848,7 @@ def list_processes(
|
|||||||
limit: int = Query(default=100, ge=1, le=500),
|
limit: int = Query(default=100, ge=1, le=500),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> list[ProcessRead]:
|
) -> list[ProcessRead]:
|
||||||
|
_ensure_default_operation_foundation(db)
|
||||||
rows = db.execute(
|
rows = db.execute(
|
||||||
select(
|
select(
|
||||||
Process.id.label("process_id"),
|
Process.id.label("process_id"),
|
||||||
|
|||||||
@ -0,0 +1,59 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from sqlalchemy import BigInteger, create_engine, func, select
|
||||||
|
from sqlalchemy.ext.compiler import compiles
|
||||||
|
from sqlalchemy.orm import Session, sessionmaker
|
||||||
|
|
||||||
|
|
||||||
|
@compiles(BigInteger, "sqlite")
|
||||||
|
def _compile_bigint_for_sqlite(type_, compiler, **kw) -> str:
|
||||||
|
_ = type_, compiler, kw
|
||||||
|
return "INTEGER"
|
||||||
|
|
||||||
|
|
||||||
|
import app.models.master_data # noqa: E402,F401
|
||||||
|
import app.models.miniapp # noqa: E402,F401
|
||||||
|
import app.models.operations # noqa: E402,F401
|
||||||
|
import app.models.org # noqa: E402,F401
|
||||||
|
import app.models.planning # noqa: E402,F401
|
||||||
|
import app.models.sales # noqa: E402,F401
|
||||||
|
from app.api.routes import master_data # noqa: E402
|
||||||
|
from app.models.base import Base # noqa: E402
|
||||||
|
from app.models.operations import Process, WorkCenter # noqa: E402
|
||||||
|
from app.models.org import Department # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
class MasterDataDefaultOperationFoundationTest(unittest.TestCase):
|
||||||
|
def setUp(self) -> None:
|
||||||
|
engine = create_engine("sqlite+pysqlite:///:memory:", future=True)
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
|
self.SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)
|
||||||
|
self.db: Session = self.SessionLocal()
|
||||||
|
|
||||||
|
def tearDown(self) -> None:
|
||||||
|
self.db.close()
|
||||||
|
|
||||||
|
def test_list_processes_seeds_default_process_for_empty_system(self) -> None:
|
||||||
|
self.assertEqual(self.db.scalar(select(func.count(Process.id))), 0)
|
||||||
|
|
||||||
|
rows = master_data.list_processes(limit=100, db=self.db)
|
||||||
|
|
||||||
|
self.assertEqual(len(rows), 1)
|
||||||
|
self.assertEqual(rows[0].process_code, "PROC-DEFAULT")
|
||||||
|
self.assertEqual(rows[0].process_name, "系统默认工序")
|
||||||
|
self.assertEqual(self.db.scalar(select(func.count(Process.id))), 1)
|
||||||
|
|
||||||
|
def test_list_work_centers_seeds_default_work_center_for_empty_system(self) -> None:
|
||||||
|
self.assertEqual(self.db.scalar(select(func.count(Department.id))), 0)
|
||||||
|
self.assertEqual(self.db.scalar(select(func.count(WorkCenter.id))), 0)
|
||||||
|
|
||||||
|
rows = master_data.list_work_centers(limit=100, db=self.db)
|
||||||
|
|
||||||
|
self.assertEqual(len(rows), 1)
|
||||||
|
self.assertEqual(rows[0].center_code, "WC-DEFAULT")
|
||||||
|
self.assertEqual(rows[0].center_name, "系统默认工作中心")
|
||||||
|
self.assertEqual(rows[0].dept_name, "总公司")
|
||||||
|
self.assertEqual(self.db.scalar(select(func.count(Department.id))), 1)
|
||||||
|
self.assertEqual(self.db.scalar(select(func.count(WorkCenter.id))), 1)
|
||||||
47
frontend/src/views/ProductSpecLiteView.test.js
Normal file
47
frontend/src/views/ProductSpecLiteView.test.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { describe, it } from "node:test";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const source = readFileSync(join(__dirname, "ProductSpecLiteView.vue"), "utf8");
|
||||||
|
|
||||||
|
describe("ProductSpecLiteView operation foundation loading", () => {
|
||||||
|
it("loads process and work-center master data sequentially so backend default seeding can settle", () => {
|
||||||
|
assert.match(source, /const processRows = await fetchResource\("\/master-data\/processes", \[\]\);/);
|
||||||
|
assert.match(source, /const centerRows = await fetchResource\("\/master-data\/work-centers", \[\]\);/);
|
||||||
|
assert.ok(
|
||||||
|
source.indexOf('fetchResource("/master-data/processes", [])') <
|
||||||
|
source.indexOf('fetchResource("/master-data/work-centers", [])')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("prepares route operations before creating a new product so validation cannot leave partial records", () => {
|
||||||
|
assert.match(source, /async function ensureOperationFoundationReady\(\)/);
|
||||||
|
assert.match(source, /await ensureOperationFoundationReady\(\);/);
|
||||||
|
assert.match(source, /const preparedRouteOperations = buildRouteOperationsPayload\(processRowsForSave\);/);
|
||||||
|
assert.ok(
|
||||||
|
source.indexOf("const preparedRouteOperations = buildRouteOperationsPayload(processRowsForSave);") <
|
||||||
|
source.indexOf('await postResource("/master-data/products", productPayload)')
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("cleans newly created product-spec records if a later save step fails", () => {
|
||||||
|
assert.match(source, /let createdProductItemId = null;/);
|
||||||
|
assert.match(source, /const createdMiniappPayloads = \[\];/);
|
||||||
|
assert.match(source, /createdProductItemId = productItemId;/);
|
||||||
|
assert.match(source, /createdMiniappPayloads\.push\(payload\);/);
|
||||||
|
assert.match(source, /await deleteResource\(buildMiniappDeletePath\(payload\)\)\.catch\(\(\) => null\);/);
|
||||||
|
assert.match(source, /await deleteResource\(`\/master-data\/product-specs\/\$\{createdProductItemId\}`\)\.catch\(\(\) => null\);/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses a one-screen table layout so the action and pagination areas do not get pushed into the edge", () => {
|
||||||
|
assert.match(source, /class="table-wrap product-spec-table-wrap"/);
|
||||||
|
assert.match(source, /class="data-table compact-table product-spec-table"/);
|
||||||
|
assert.match(source, /<colgroup>[\s\S]*product-spec-col-action[\s\S]*<\/colgroup>/);
|
||||||
|
assert.match(source, /\.product-spec-table-wrap\.table-view-wide,[\s\S]*?overflow-x:\s*hidden !important;/);
|
||||||
|
assert.match(source, /\.product-spec-table[\s\S]*?table-layout:\s*fixed !important;/);
|
||||||
|
assert.match(source, /\.product-spec-table\s+\.action-row[\s\S]*?width:\s*100% !important;/);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -23,8 +23,25 @@
|
|||||||
placeholder="搜索考勤点、产品编码、名称、版本、原材料、小程序工序"
|
placeholder="搜索考勤点、产品编码、名称、版本、原材料、小程序工序"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="table-wrap">
|
<div class="table-wrap product-spec-table-wrap">
|
||||||
<table class="data-table compact-table">
|
<table class="data-table compact-table product-spec-table">
|
||||||
|
<colgroup>
|
||||||
|
<col class="product-spec-col-code" />
|
||||||
|
<col class="product-spec-col-attendance" />
|
||||||
|
<col class="product-spec-col-project" />
|
||||||
|
<col class="product-spec-col-product" />
|
||||||
|
<col class="product-spec-col-version" />
|
||||||
|
<col class="product-spec-col-weight" />
|
||||||
|
<col class="product-spec-col-material" />
|
||||||
|
<col class="product-spec-col-gross" />
|
||||||
|
<col class="product-spec-col-rate" />
|
||||||
|
<col class="product-spec-col-rate" />
|
||||||
|
<col class="product-spec-col-price" />
|
||||||
|
<col class="product-spec-col-process" />
|
||||||
|
<col class="product-spec-col-beat" />
|
||||||
|
<col class="product-spec-col-status" />
|
||||||
|
<col class="product-spec-col-action" />
|
||||||
|
</colgroup>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>产品编码</th>
|
<th>产品编码</th>
|
||||||
@ -73,6 +90,7 @@
|
|||||||
<td class="action-row">
|
<td class="action-row">
|
||||||
<button
|
<button
|
||||||
class="ghost-button"
|
class="ghost-button"
|
||||||
|
data-semantic-label="编辑"
|
||||||
type="button"
|
type="button"
|
||||||
:disabled="!canModifySpec(row)"
|
:disabled="!canModifySpec(row)"
|
||||||
:title="canModifySpec(row) ? '编辑' : '已停用的产品需规只读,不允许修改'"
|
:title="canModifySpec(row) ? '编辑' : '已停用的产品需规只读,不允许修改'"
|
||||||
@ -82,6 +100,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="ghost-button danger-ghost"
|
class="ghost-button danger-ghost"
|
||||||
|
data-semantic-label="删除"
|
||||||
type="button"
|
type="button"
|
||||||
:disabled="!canModifySpec(row)"
|
:disabled="!canModifySpec(row)"
|
||||||
:title="canModifySpec(row) ? '删除' : '已停用的产品需规只读,不允许删除'"
|
:title="canModifySpec(row) ? '删除' : '已停用的产品需规只读,不允许删除'"
|
||||||
@ -97,7 +116,7 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<PaginationBar v-model:page="page" v-model:page-size="pageSize" :total="filteredRows.length" />
|
<PaginationBar class="product-spec-pagination" v-model:page="page" v-model:page-size="pageSize" :total="filteredRows.length" />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<FormDrawer
|
<FormDrawer
|
||||||
@ -856,19 +875,23 @@ function buildAutoRouteOperation(processRow, index) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildRoutePayload(product, processRowsForSave) {
|
function buildRouteOperationsPayload(processRowsForSave) {
|
||||||
const productItemId = product.item_id;
|
|
||||||
const processRows = buildProcessGroupForRoute(processRowsForSave);
|
const processRows = buildProcessGroupForRoute(processRowsForSave);
|
||||||
if (!processRows.length) {
|
if (!processRows.length) {
|
||||||
throw new Error("请至少维护一道小程序工序");
|
throw new Error("请至少维护一道小程序工序");
|
||||||
}
|
}
|
||||||
|
return processRows.map((processRow, index) => buildAutoRouteOperation(processRow, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildRoutePayload(product, processRowsForSave, preparedOperations = null) {
|
||||||
|
const productItemId = product.item_id;
|
||||||
return {
|
return {
|
||||||
route_name: `${form.item_name} 工艺路线`,
|
route_name: `${form.item_name} 工艺路线`,
|
||||||
product_item_id: productItemId,
|
product_item_id: productItemId,
|
||||||
version_no: form.version_no,
|
version_no: form.version_no,
|
||||||
is_default: true,
|
is_default: true,
|
||||||
status: "ACTIVE",
|
status: "ACTIVE",
|
||||||
operations: processRows.map((processRow, index) => buildAutoRouteOperation(processRow, index))
|
operations: preparedOperations || buildRouteOperationsPayload(processRowsForSave)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -931,16 +954,16 @@ watch(calculatedLossRate, (lossRate) => {
|
|||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
async function fetchMasterData() {
|
async function fetchMasterData() {
|
||||||
const [productRows, bomRows, routeRows, materialRows, miniappProductRows, pointRows, processRows, centerRows] = await Promise.all([
|
const [productRows, bomRows, routeRows, materialRows, miniappProductRows, pointRows] = await Promise.all([
|
||||||
fetchResource("/master-data/products?limit=200", []),
|
fetchResource("/master-data/products?limit=200", []),
|
||||||
fetchResource("/master-data/boms?limit=500", []),
|
fetchResource("/master-data/boms?limit=500", []),
|
||||||
fetchResource("/master-data/process-routes?limit=500", []),
|
fetchResource("/master-data/process-routes?limit=500", []),
|
||||||
fetchResource("/master-data/materials?limit=200", []),
|
fetchResource("/master-data/materials?limit=200", []),
|
||||||
fetchResource("/master-data/miniapp-products?limit=1000", []),
|
fetchResource("/master-data/miniapp-products?limit=1000", []),
|
||||||
fetchResource("/master-data/miniapp-attendance-points", []),
|
fetchResource("/master-data/miniapp-attendance-points", [])
|
||||||
fetchResource("/master-data/processes", []),
|
|
||||||
fetchResource("/master-data/work-centers", [])
|
|
||||||
]);
|
]);
|
||||||
|
const processRows = await fetchResource("/master-data/processes", []);
|
||||||
|
const centerRows = await fetchResource("/master-data/work-centers", []);
|
||||||
products.value = productRows;
|
products.value = productRows;
|
||||||
boms.value = bomRows;
|
boms.value = bomRows;
|
||||||
routes.value = routeRows;
|
routes.value = routeRows;
|
||||||
@ -951,6 +974,18 @@ async function fetchMasterData() {
|
|||||||
workCenters.value = centerRows;
|
workCenters.value = centerRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function ensureOperationFoundationReady() {
|
||||||
|
if (!processes.value.length) {
|
||||||
|
processes.value = await fetchResource("/master-data/processes", []);
|
||||||
|
}
|
||||||
|
if (!workCenters.value.length) {
|
||||||
|
workCenters.value = await fetchResource("/master-data/work-centers", []);
|
||||||
|
}
|
||||||
|
if (!processes.value.length || !workCenters.value.length) {
|
||||||
|
throw new Error("系统无法准备默认工艺基础资料,请稍后重试或联系管理员");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function syncMiniappInBackground() {
|
async function syncMiniappInBackground() {
|
||||||
if (miniappSyncing.value) {
|
if (miniappSyncing.value) {
|
||||||
return;
|
return;
|
||||||
@ -977,8 +1012,12 @@ async function submitSpec() {
|
|||||||
feedbackMessage.value = "";
|
feedbackMessage.value = "";
|
||||||
errorMessage.value = "";
|
errorMessage.value = "";
|
||||||
submitting.value = true;
|
submitting.value = true;
|
||||||
|
let createdProductItemId = null;
|
||||||
|
const createdMiniappPayloads = [];
|
||||||
try {
|
try {
|
||||||
const processRowsForSave = normalizeProcessFormRowsForSave();
|
const processRowsForSave = normalizeProcessFormRowsForSave();
|
||||||
|
await ensureOperationFoundationReady();
|
||||||
|
const preparedRouteOperations = buildRouteOperationsPayload(processRowsForSave);
|
||||||
const productPayload = {
|
const productPayload = {
|
||||||
item_name: form.item_name,
|
item_name: form.item_name,
|
||||||
specification: editingRow.value?.specification || null,
|
specification: editingRow.value?.specification || null,
|
||||||
@ -997,6 +1036,9 @@ async function submitSpec() {
|
|||||||
? await putResource(`/master-data/products/${editingRow.value.item_id}`, productPayload)
|
? await putResource(`/master-data/products/${editingRow.value.item_id}`, productPayload)
|
||||||
: await postResource("/master-data/products", productPayload);
|
: await postResource("/master-data/products", productPayload);
|
||||||
const productItemId = product.item_id;
|
const productItemId = product.item_id;
|
||||||
|
if (!shouldUpdateErpProduct) {
|
||||||
|
createdProductItemId = productItemId;
|
||||||
|
}
|
||||||
const canUpdateExistingSpec = editingRow.value && Number(editingRow.value.item_id) === Number(productItemId);
|
const canUpdateExistingSpec = editingRow.value && Number(editingRow.value.item_id) === Number(productItemId);
|
||||||
if (canUpdateExistingSpec && editingRow.value.bom?.bom_id) {
|
if (canUpdateExistingSpec && editingRow.value.bom?.bom_id) {
|
||||||
await putResource(`/master-data/boms/${editingRow.value.bom.bom_id}`, buildBomPayload(productItemId));
|
await putResource(`/master-data/boms/${editingRow.value.bom.bom_id}`, buildBomPayload(productItemId));
|
||||||
@ -1004,19 +1046,27 @@ async function submitSpec() {
|
|||||||
await postResource("/master-data/boms", buildBomPayload(productItemId));
|
await postResource("/master-data/boms", buildBomPayload(productItemId));
|
||||||
}
|
}
|
||||||
if (canUpdateExistingSpec && editingRow.value.route?.route_id) {
|
if (canUpdateExistingSpec && editingRow.value.route?.route_id) {
|
||||||
await putResource(`/master-data/process-routes/${editingRow.value.route.route_id}`, buildRoutePayload(product, processRowsForSave));
|
await putResource(`/master-data/process-routes/${editingRow.value.route.route_id}`, buildRoutePayload(product, processRowsForSave, preparedRouteOperations));
|
||||||
} else {
|
} else {
|
||||||
await postResource("/master-data/process-routes", buildRoutePayload(product, processRowsForSave));
|
await postResource("/master-data/process-routes", buildRoutePayload(product, processRowsForSave, preparedRouteOperations));
|
||||||
}
|
}
|
||||||
const miniappPayloads = buildMiniappProductPayloads(product, processRowsForSave);
|
const miniappPayloads = buildMiniappProductPayloads(product, processRowsForSave);
|
||||||
for (const payload of miniappPayloads) {
|
for (const payload of miniappPayloads) {
|
||||||
await postResource("/master-data/miniapp-products?sync=false", payload);
|
await postResource("/master-data/miniapp-products?sync=false", payload);
|
||||||
|
createdMiniappPayloads.push(payload);
|
||||||
}
|
}
|
||||||
await postResource("/master-data/miniapp-materials/sync?force=true", {});
|
await postResource("/master-data/miniapp-materials/sync?force=true", {});
|
||||||
feedbackMessage.value = `产品需规 ${product.item_code} 已保存`;
|
feedbackMessage.value = `产品需规 ${product.item_code} 已保存`;
|
||||||
drawerOpen.value = false;
|
drawerOpen.value = false;
|
||||||
await loadAll();
|
await loadAll();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (createdProductItemId) {
|
||||||
|
for (const payload of createdMiniappPayloads) {
|
||||||
|
await deleteResource(buildMiniappDeletePath(payload)).catch(() => null);
|
||||||
|
}
|
||||||
|
await deleteResource(`/master-data/product-specs/${createdProductItemId}`).catch(() => null);
|
||||||
|
await fetchMasterData().catch(() => null);
|
||||||
|
}
|
||||||
errorMessage.value = error.message || "产品需规保存失败";
|
errorMessage.value = error.message || "产品需规保存失败";
|
||||||
} finally {
|
} finally {
|
||||||
submitting.value = false;
|
submitting.value = false;
|
||||||
@ -1038,6 +1088,177 @@ onActivated(async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.product-spec-table-wrap,
|
||||||
|
.product-spec-table-wrap.table-view-wide,
|
||||||
|
.product-spec-table-wrap.table-view-overview {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-table,
|
||||||
|
.product-spec-table.data-table-wide,
|
||||||
|
.product-spec-table.data-table-overview,
|
||||||
|
.product-spec-table-wrap.table-view-wide .product-spec-table,
|
||||||
|
.product-spec-table-wrap.table-view-overview .product-spec-table {
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
table-layout: fixed !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-code {
|
||||||
|
width: 8%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-attendance {
|
||||||
|
width: 5.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-project {
|
||||||
|
width: 8.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-product {
|
||||||
|
width: 10.5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-version {
|
||||||
|
width: 4.2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-weight {
|
||||||
|
width: 4.6%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-material {
|
||||||
|
width: 11%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-gross {
|
||||||
|
width: 5.4%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-rate {
|
||||||
|
width: 5.7%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-price {
|
||||||
|
width: 5.4%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-process {
|
||||||
|
width: 6.2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-beat {
|
||||||
|
width: 5.2%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-status {
|
||||||
|
width: 5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-col-action {
|
||||||
|
width: 8.6%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-table th,
|
||||||
|
.product-spec-table td,
|
||||||
|
.product-spec-table.data-table-wide th,
|
||||||
|
.product-spec-table.data-table-overview th,
|
||||||
|
.product-spec-table-wrap.table-view-wide .product-spec-table th,
|
||||||
|
.product-spec-table-wrap.table-view-overview .product-spec-table th {
|
||||||
|
min-width: 0 !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-table th,
|
||||||
|
.product-spec-table th :deep(.sortable-th-button),
|
||||||
|
.product-spec-table th :deep(.sortable-th-label) {
|
||||||
|
overflow: visible !important;
|
||||||
|
text-overflow: clip !important;
|
||||||
|
white-space: normal !important;
|
||||||
|
word-break: keep-all !important;
|
||||||
|
overflow-wrap: anywhere !important;
|
||||||
|
line-height: 1.22;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-table th :deep(.sortable-th-button) {
|
||||||
|
grid-template-columns: minmax(0, 1fr) 16px !important;
|
||||||
|
column-gap: 4px !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-table th :deep(.sortable-th-label) {
|
||||||
|
min-width: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-table td:not(.action-row),
|
||||||
|
.product-spec-table.data-table-wide td:not(.action-row),
|
||||||
|
.product-spec-table.data-table-overview td:not(.action-row) {
|
||||||
|
min-width: 0 !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
text-overflow: ellipsis !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-table .action-row,
|
||||||
|
.product-spec-table.data-table-wide .action-row,
|
||||||
|
.product-spec-table.data-table-overview .action-row {
|
||||||
|
width: 100% !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
padding-right: 8px !important;
|
||||||
|
padding-left: 8px !important;
|
||||||
|
text-align: center !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-table-wrap.table-view-wide .product-spec-table:has(td.action-row) thead th:last-child,
|
||||||
|
.product-spec-table-wrap.table-view-overview .product-spec-table:has(td.action-row) thead th:last-child,
|
||||||
|
.product-spec-table.data-table-wide:has(td.action-row) thead th:last-child,
|
||||||
|
.product-spec-table.data-table-overview:has(td.action-row) thead th:last-child {
|
||||||
|
width: auto !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-table .action-row .ghost-button,
|
||||||
|
.product-spec-table .action-row :deep(.semantic-action-button) {
|
||||||
|
width: calc((100% - 6px) / 2) !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
max-width: none !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
margin-right: 0 !important;
|
||||||
|
padding: 0 6px !important;
|
||||||
|
gap: 5px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-table .action-row .ghost-button + .ghost-button,
|
||||||
|
.product-spec-table .action-row :deep(.semantic-action-button + .semantic-action-button) {
|
||||||
|
margin-left: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-table .action-row :deep(.semantic-action-label) {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-pagination {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-spec-pagination :deep(.pagination-actions) {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.process-field-list {
|
.process-field-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user