| New file |
| | |
| | | import React from 'react'; |
| | | import { NumberInput } from 'react-admin'; |
| | | import { maxDecimalPlaces } from '@/utils/common'; |
| | | |
| | | const MAX_DECIMALS = 6; |
| | | const QUANTITY_VALIDATE = [maxDecimalPlaces(MAX_DECIMALS, '最多6位小数')]; |
| | | |
| | | /** |
| | | * 数量输入框:支持最多 6 位小数,超过时提示并阻止提交 |
| | | */ |
| | | const QuantityInput = (props) => { |
| | | const { validate = [], helperText, ...rest } = props; |
| | | const mergedValidate = Array.isArray(validate) ? [...QUANTITY_VALIDATE, ...validate] : [...QUANTITY_VALIDATE, validate]; |
| | | return ( |
| | | <NumberInput |
| | | validate={mergedValidate} |
| | | helperText={helperText ?? '最多6位小数'} |
| | | {...rest} |
| | | /> |
| | | ); |
| | | }; |
| | | |
| | | export default QuantityInput; |
| | |
| | | import DialogCloseButton from "../components/DialogCloseButton"; |
| | | import StatusSelectInput from "../components/StatusSelectInput"; |
| | | import MemoInput from "../components/MemoInput"; |
| | | import QuantityInput from "../components/QuantityInput"; |
| | | |
| | | const LocItemCreate = (props) => { |
| | | const { open, setOpen } = props; |
| | |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.locItem.anfme" |
| | | source="anfme" |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.locItem.qty" |
| | | source="qty" |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.locItem.workQty" |
| | | source="workQty" |
| | | /> |
| | |
| | | import { useWatch, useFormContext } from "react-hook-form"; |
| | | import { Stack, Grid, Box, Typography } from '@mui/material'; |
| | | import * as Common from '@/utils/common'; |
| | | import QuantityInput from "../components/QuantityInput"; |
| | | import { EDIT_MODE, REFERENCE_INPUT_PAGESIZE } from '@/config/setting'; |
| | | import EditBaseAside from "../components/EditBaseAside"; |
| | | import CustomerTopToolBar from "../components/EditTopToolBar"; |
| | |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.locItem.anfme" |
| | | source="anfme" |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.locItem.qty" |
| | | source="qty" |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.locItem.workQty" |
| | | source="workQty" |
| | | /> |
| | |
| | | <TextField source="maktx" label="table.field.locItem.maktx" />, |
| | | <TextField source="matnrCode" label="table.field.locItem.matnrCode" />, |
| | | <TextField source="unit" label="table.field.locItem.unit" />, |
| | | <NumberField source="anfme" label="table.field.locItem.anfme" />, |
| | | <NumberField source="anfme" label="table.field.locItem.anfme" options={{ maximumFractionDigits: 6 }} />, |
| | | <TextField source="batch" label="table.field.locItem.batch" />, |
| | | <NumberField source="splrId" label="table.field.locItem.splrId" />, |
| | | <TextField source="spec" label="table.field.locItem.spec" />, |
| | |
| | | <TextField source="type$" label="table.field.purchase.type" /> |
| | | <TextField source="wkType$" label="table.field.purchase.wkType" /> |
| | | <TextField source="source" label="table.field.purchase.source" /> |
| | | <NumberField source="anfme" label="table.field.purchase.anfme" /> |
| | | <NumberField source="qty" label="table.field.purchase.qty" /> |
| | | <NumberField source="anfme" label="table.field.purchase.anfme" options={{ maximumFractionDigits: 6 }} /> |
| | | <NumberField source="qty" label="table.field.purchase.qty" options={{ maximumFractionDigits: 6 }} /> |
| | | <TextField source="channel" label="table.field.purchase.channel" /> |
| | | <TextField source="platCode" label="table.field.purchase.platCode" /> |
| | | <DateField source="preArr" label="table.field.purchase.preArr" showTime /> |
| | |
| | | <TextField source="projectCode" label="table.field.asnOrderItem.projectCode" />, |
| | | <TextField source="spec" label="table.field.asnOrderItem.spec" />, |
| | | <TextField source="model" label="table.field.asnOrderItem.model" />, |
| | | <NumberField source="anfme" label="table.field.asnOrderItem.anfme" />, |
| | | <NumberField source="qty" label="table.field.asnOrderItem.qty" />, |
| | | <NumberField source="anfme" label="table.field.asnOrderItem.anfme" options={{ maximumFractionDigits: 6 }} />, |
| | | <NumberField source="qty" label="table.field.asnOrderItem.qty" options={{ maximumFractionDigits: 6 }} />, |
| | | <TextField source="stockUnit" label="table.field.asnOrderItem.stockUnit" />, |
| | | <NumberField source="purQty" label="table.field.asnOrderItem.purQty" />, |
| | | <NumberField source="purQty" label="table.field.asnOrderItem.purQty" options={{ maximumFractionDigits: 6 }} />, |
| | | <TextField source="purUnit" label="table.field.asnOrderItem.purUnit" />, |
| | | <TextField source="splrCode" label="table.field.asnOrderItem.splrCode" />, |
| | | <TextField source="splrName" label="table.field.asnOrderItem.splrName" />, |
| | |
| | | <NumberField source="poId" label="table.field.asnOrder.poId" /> |
| | | <TextField source="type$" label="table.field.asnOrder.type" /> |
| | | <TextField cellClassName="wkType" source="wkType$" label="table.field.asnOrder.wkType" /> |
| | | <NumberField source="anfme" label="table.field.asnOrder.anfme" /> |
| | | <NumberField source="qty" label="table.field.asnOrder.qty" /> |
| | | <NumberField source="anfme" label="table.field.asnOrder.anfme" options={{ maximumFractionDigits: 6 }} /> |
| | | <NumberField source="qty" label="table.field.asnOrder.qty" options={{ maximumFractionDigits: 6 }} /> |
| | | <DateField source="arrTime" label="table.field.asnOrder.arrTime" showTime /> |
| | | <TextField source="rleStatus$" label="table.field.asnOrder.rleStatus" sortable={false} /> |
| | | <TextField source="logisNo" label="table.field.asnOrder.logisNo" /> |
| | |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import AsnWareModal from "./AsnWareModal"; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import _, { set } from 'lodash'; |
| | | import "./asnOrder.css"; |
| | | |
| | |
| | | minWidth: 100, |
| | | flex: 1, |
| | | editable: true, |
| | | valueFormatter: (val) => val < 0 ? 0 : val, |
| | | valueFormatter: (val) => formatQuantity(val != null && val >= 0 ? val : 0), |
| | | headerClassName: "custom", |
| | | }, |
| | | // { |
| | |
| | | useListContext, |
| | | } from 'react-admin'; |
| | | import PanelTypography from "../../components/PanelTypography"; |
| | | import * as Common from '@/utils/common' |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import { styled } from "@mui/material/styles"; |
| | | import request from '@/utils/request'; |
| | | import debounce from 'lodash/debounce'; |
| | |
| | | }, |
| | | { |
| | | field: 'anfme', |
| | | headerName: translate('table.field.asnOrderItem.purQty') |
| | | headerName: translate('table.field.asnOrderItem.purQty'), |
| | | valueFormatter: (v) => formatQuantity(v) |
| | | }, |
| | | { |
| | | field: 'stockUnit', |
| | |
| | | }, |
| | | { |
| | | field: 'qty', |
| | | headerName: translate('table.field.asnOrderItem.qty') |
| | | headerName: translate('table.field.asnOrderItem.qty'), |
| | | valueFormatter: (v) => formatQuantity(v) |
| | | }, |
| | | { |
| | | field: 'splrBatch', |
| | |
| | | <TextField source="matnrCode" label="table.field.asnOrderItem.matnrCode" />, |
| | | <TextField source="maktx" label="table.field.asnOrderItem.maktx" />, |
| | | <TextField source="splrBatch" label="table.field.asnOrderItem.splrBatch" />, |
| | | <NumberField source="anfme" label="table.field.asnOrderItem.anfme" />, |
| | | <NumberField source="qty" label="table.field.asnOrderItem.qty" />, |
| | | <NumberField source="purQty" label="table.field.asnOrderItem.purQty" />, |
| | | <NumberField source="anfme" label="table.field.asnOrderItem.anfme" options={{ maximumFractionDigits: 6 }} />, |
| | | <NumberField source="qty" label="table.field.asnOrderItem.qty" options={{ maximumFractionDigits: 6 }} />, |
| | | <NumberField source="purQty" label="table.field.asnOrderItem.purQty" options={{ maximumFractionDigits: 6 }} />, |
| | | // <TextField source="splrName" label="table.field.asnOrderItem.splrName" />, |
| | | <TextField source="isptResult$" label="table.field.asnOrderItem.isptResult" />, |
| | | // <TextField source="trackCode" label="table.field.asnOrderItem.barcode" />, |
| | |
| | | import { useForm, Controller, useWatch, FormProvider, useFormContext } from "react-hook-form"; |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import { Add, Edit, Delete } from '@mui/icons-material'; |
| | | import _, { set } from 'lodash'; |
| | | import { DataGrid, useGridApiRef, GRID_DATE_COL_DEF, GRID_DATETIME_COL_DEF, getGridDateOperators, useGridApiContext } from '@mui/x-data-grid'; |
| | |
| | | minWidth: 100, |
| | | flex: 1, |
| | | editable: true, |
| | | valueFormatter: (val) => val < 0 ? 0 : val, |
| | | valueFormatter: (val) => formatQuantity(val != null && val >= 0 ? val : 0), |
| | | headerClassName: "custom", |
| | | }, |
| | | { |
| | |
| | | type: 'number', |
| | | minWidth: 100, |
| | | flex: 1, |
| | | valueFormatter: (val) => val < 0 ? 0 : val, |
| | | valueFormatter: (val) => formatQuantity(val != null && val >= 0 ? val : 0), |
| | | }, |
| | | { |
| | | field: 'unit', |
| | |
| | | import MatnrInfoModal from "./MatnrInfoModal"; |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import { Add, Edit, Delete } from '@mui/icons-material'; |
| | | import { DataGrid, useGridApiRef } from '@mui/x-data-grid'; |
| | | import DictionarySelect from "../../components/DictionarySelect"; |
| | |
| | | minWidth: 120, |
| | | flex: 1, |
| | | editable: true, |
| | | valueFormatter: (val) => val < 0 ? 0 : val, |
| | | valueFormatter: (val) => formatQuantity(val != null && val >= 0 ? val : 0), |
| | | headerClassName: "custom", |
| | | }, |
| | | { |
| | |
| | | import { EDIT_MODE, DEFAULT_START_PAGE, DEFAULT_PAGE_SIZE, REFERENCE_INPUT_PAGESIZE } from '@/config/setting'; |
| | | import { useTranslate, useNotify, useRefresh } from 'react-admin'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import { DataGrid } from '@mui/x-data-grid'; |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import TreeSelectInput from "@/page/components/TreeSelectInput"; |
| | |
| | | }) |
| | | } |
| | | |
| | | const getRowId = (row) => (row.locUseStatus$ != null ? `${row.id}_${row.locUseStatus$}` : String(row.id)); |
| | | |
| | | const handleSubmit = () => { |
| | | const hasarr = data.map(el => +el.matnrId); |
| | | const selectedData = selectedRows |
| | | .filter((item) => !hasarr.includes(item)) |
| | | .map((id) => tableData.find((row) => row.id === id)) |
| | | .filter(Boolean); |
| | | .map((id) => tableData.find((row) => getRowId(row) === id)) |
| | | .filter(Boolean) |
| | | .filter((row) => !hasarr.includes(row.id)); |
| | | const deduped = [...new Map(selectedData.map((s) => [s.id, s])).values()]; |
| | | const value = deduped.map((el, i) => { |
| | | const dynamicFields = dyFields.reduce((acc, item) => { |
| | |
| | | setDyFields={setDyFields} |
| | | selectedRows={selectedRows} |
| | | setSelectedRows={setSelectedRows} |
| | | getRowId={getRowId} |
| | | /> |
| | | </Box> |
| | | </DialogContent> |
| | |
| | | |
| | | export default MatnrInfoModal; |
| | | |
| | | const AsnWareModalTable = ({ tableData, setTableData, page, isLoading, pageSize, setPage, rowCount, selectedRows, setSelectedRows, dyFields, setDyFields }) => { |
| | | const AsnWareModalTable = ({ tableData, setTableData, page, isLoading, pageSize, setPage, rowCount, selectedRows, setSelectedRows, dyFields, setDyFields, getRowId }) => { |
| | | const translate = useTranslate(); |
| | | const notify = useNotify(); |
| | | |
| | |
| | | { field: 'unit', headerName: translate('table.field.matnr.unit'), width: 100 }, |
| | | { field: 'purchaseUnit', headerName: translate('table.field.matnr.purUnit'), width: 100 }, |
| | | { field: 'stockUnit', headerName: translate('table.field.matnr.stockUnit'), width: 100 }, |
| | | { field: 'stockQty', headerName: translate('table.field.matnr.stockQty') || '库存数量', width: 110, type: 'number', valueFormatter: (v) => (v != null ? Number(v) : 0) }, |
| | | { field: 'stockQty', headerName: translate('table.field.matnr.stockQty') || '库存数量', width: 110, type: 'number', valueFormatter: (v) => formatQuantity(v) }, |
| | | { field: 'locUseStatus$', headerName: translate('table.field.loc.useStatus'), width: 120 }, |
| | | { field: 'locCodes$', headerName: translate('table.field.loc.locCode'), width: 180, flex: 1 }, |
| | | { field: 'stockLeval$', headerName: translate('table.field.matnr.stockLevel'), width: 100, sortable: false }, |
| | |
| | | <div style={{ height: 400, width: '100%' }}> |
| | | <DataGrid |
| | | size="small" |
| | | getRowId={getRowId} |
| | | rows={tableData} |
| | | columns={columns} |
| | | checkboxSelection |
| | |
| | | <TextField source="matnrCode" label="table.field.outStockItem.matnrCode" />, |
| | | <TextField source="maktx" label="table.field.outStockItem.maktx" />, |
| | | <TextField source="platOrderCode" label="table.field.outStockItem.platOrderCode" />, |
| | | <NumberField source="anfme" label="table.field.outStockItem.anfme" />, |
| | | <NumberField source="purQty" label="table.field.outStockItem.purQty" />, |
| | | <NumberField source="workQty" label="table.field.outStockItem.workQty" />, |
| | | <NumberField source="qty" label="table.field.outStockItem.qty" />, |
| | | <NumberField source="anfme" label="table.field.outStockItem.anfme" options={{ maximumFractionDigits: 6 }} />, |
| | | <NumberField source="purQty" label="table.field.outStockItem.purQty" options={{ maximumFractionDigits: 6 }} />, |
| | | <NumberField source="workQty" label="table.field.outStockItem.workQty" options={{ maximumFractionDigits: 6 }} />, |
| | | <NumberField source="qty" label="table.field.outStockItem.qty" options={{ maximumFractionDigits: 6 }} />, |
| | | <TextField source="stockUnit" label="table.field.outStockItem.stockUnit" />, |
| | | <TextField source="splrBatch" label="table.field.outStockItem.splrBatch" />, |
| | | <TextField source="purUnit" label="table.field.outStockItem.purUnit" />, |
| | |
| | | <TextField source="poCode" label="table.field.outStock.poCode" /> |
| | | <TextField source="type$" label="table.field.outStock.type" /> |
| | | <TextField cellClassName="wkType" source="wkType$" label="table.field.outStock.wkType" /> |
| | | <NumberField source="anfme" label="table.field.outStock.anfme" /> |
| | | <NumberField source="workQty" label="table.field.outStock.workQty" /> |
| | | <NumberField source="qty" label="table.field.outStock.qty" /> |
| | | <NumberField source="anfme" label="table.field.outStock.anfme" options={{ maximumFractionDigits: 6 }} /> |
| | | <NumberField source="workQty" label="table.field.outStock.workQty" options={{ maximumFractionDigits: 6 }} /> |
| | | <NumberField source="qty" label="table.field.outStock.qty" options={{ maximumFractionDigits: 6 }} /> |
| | | <TextField source="logisNo" label="table.field.outStock.logisNo" /> |
| | | <TextField source="rleStatus$" label="table.field.outStock.rleStatus" sortable={false} /> |
| | | <TextField source="updateBy$" label="common.field.updateBy" /> |
| | |
| | | <TextField source="matnrCode" label="table.field.deliveryItem.matnrCode" /> |
| | | <TextField source="maktx" label="table.field.deliveryItem.matnrName" /> |
| | | <TextField source="unit" label="table.field.deliveryItem.unit" /> |
| | | <NumberField source="anfme" label="table.field.deliveryItem.anfme" /> |
| | | <NumberField source="workQty" label="table.field.outStockItem.workQty" /> |
| | | <NumberField source="anfme" label="table.field.deliveryItem.anfme" options={{ maximumFractionDigits: 6 }} /> |
| | | <NumberField source="workQty" label="table.field.outStockItem.workQty" options={{ maximumFractionDigits: 6 }} /> |
| | | <TextField source="splrName" label="table.field.deliveryItem.splrName" /> |
| | | <TextField source="splrBatch" label="table.field.deliveryItem.splrBatch" /> |
| | | <TextField source="updateBy$" label="common.field.updateBy" /> |
| | |
| | | useGetList, |
| | | } from 'react-admin'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import { styled } from '@mui/material/styles'; |
| | | import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting'; |
| | | import { DataGrid, useGridApiContext, GridActionsCellItem, useGridApiRef } from '@mui/x-data-grid'; |
| | |
| | | { field: 'maktx', headerName: '物料名称', width: 190 }, |
| | | { |
| | | field: 'anfme', headerName: '出库数量', width: 110, type: 'number', editable: true, |
| | | valueFormatter: (v) => formatQuantity(v), |
| | | valueGetter: (value, row) => { |
| | | return row.anfme - row.workQty - row.qty; |
| | | }, |
| | |
| | | }, |
| | | { |
| | | field: 'workQty', headerName: '剩余数量', width: 110, type: 'number', |
| | | valueFormatter: (v) => formatQuantity(v), |
| | | valueGetter: (value, row) => { |
| | | return row.anfme - row.workQty - row.qty; |
| | | }, |
| | |
| | | import { styled } from '@mui/material/styles'; |
| | | import { DataGrid, useGridApiContext, GridActionsCellItem, useGridApiRef } from '@mui/x-data-grid'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import ConfirmationNumberOutlinedIcon from '@mui/icons-material/ConfirmationNumberOutlined'; |
| | | import CloseSharpIcon from '@mui/icons-material/CloseSharp'; |
| | | import ConfirmButton from '../../components/ConfirmButton'; |
| | |
| | | console.log('API返回的数据:', responseData); |
| | | // 处理返回的数据,确保数据结构正确 |
| | | const processedData = (responseData || []).map((item, index) => { |
| | | // 出库单下发任务:优先用后端返回的 siteNo/sitesNo,没有则默认 1001 |
| | | const defaultSiteNo = item.siteNo || item.sitesNo || '1001'; |
| | | // 如果数据有 locItem 嵌套结构,需要展开 |
| | | if (item.locItem) { |
| | | return { |
| | | ...item.locItem, |
| | | id: item.locItem.id || `temp_${index}`, |
| | | siteNo: item.siteNo, |
| | | siteNo: defaultSiteNo, |
| | | staNos: item.staNos || [], |
| | | sourceId: item.sourceId, |
| | | source: item.source |
| | |
| | | // 如果数据已经是扁平结构,直接返回 |
| | | return { |
| | | ...item, |
| | | id: item.id || `temp_${index}` |
| | | id: item.id || `temp_${index}`, |
| | | siteNo: defaultSiteNo |
| | | }; |
| | | }); |
| | | console.log('处理后的数据:', processedData); |
| | |
| | | <TextField source="poCode" label="table.field.outStockItem.poCode" /> |
| | | <TextField source="matnrCode" label="table.field.outStockItem.matnrCode" /> |
| | | <TextField source="maktx" label="table.field.outStockItem.maktx" /> |
| | | <NumberField source="anfme" label="table.field.outStockItem.anfme" /> |
| | | <NumberField source="workQty" label="table.field.outStockItem.workQty" /> |
| | | <NumberField source="qty" label="table.field.outStockItem.qty" /> |
| | | <NumberField source="anfme" label="table.field.outStockItem.anfme" options={{ maximumFractionDigits: 6 }} /> |
| | | <NumberField source="workQty" label="table.field.outStockItem.workQty" options={{ maximumFractionDigits: 6 }} /> |
| | | <NumberField source="qty" label="table.field.outStockItem.qty" options={{ maximumFractionDigits: 6 }} /> |
| | | <TextField source="stockUnit" label="table.field.outStockItem.stockUnit" /> |
| | | <TextField source="splrName" label="table.field.outStockItem.splrName" /> |
| | | </StyledDatagrid> |
| | |
| | | { field: 'matnrCode', headerName: '物料编码', width: 120 }, |
| | | { field: 'batch', headerName: '批次', width: 90 }, |
| | | { field: 'unit', headerName: '单位', width: 60 }, |
| | | { field: 'outQty', headerName: '出库数量', width: 110, }, |
| | | { field: 'outQty', headerName: '出库数量', width: 110, valueFormatter: (v) => formatQuantity(v) }, |
| | | { |
| | | field: 'anfme', headerName: '库存数量', width: 110, |
| | | renderCell: (params) => ( |
| | |
| | | return ( |
| | | hasStock ? ( |
| | | <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
| | | <span>{value}</span> |
| | | <span>{formatQuantity(value)}</span> |
| | | </Box> |
| | | ) : ( |
| | | <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
| | |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import MatnrInfoModal from "./MatnrInfoModal"; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity, hasMoreThan6Decimals } from '@/utils/common'; |
| | | import "./asnOrder.css"; |
| | | |
| | | const SelectMatnrModal = (props) => { |
| | |
| | | |
| | | const handleSubmit = async () => { |
| | | setFinally(); |
| | | if (tabelData.some((item) => hasMoreThan6Decimals(item.anfme))) { |
| | | notify('最多6位小数', { type: 'error' }); |
| | | return; |
| | | } |
| | | setDisabled(true); |
| | | try { |
| | | if (asnId === 0) { |
| | |
| | | minWidth: 100, |
| | | flex: 1, |
| | | editable: true, |
| | | valueFormatter: (val) => val < 0 ? 0 : val, |
| | | valueFormatter: (val) => formatQuantity(val != null && val >= 0 ? val : 0), |
| | | headerClassName: "custom", |
| | | }, |
| | | { |
| | |
| | | import { minHeight, padding } from "@mui/system"; |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import _, { set } from 'lodash'; |
| | | |
| | | const ManualCreate = (props) => { |
| | |
| | | minWidth: 100, |
| | | flex: 1, |
| | | editable: true, |
| | | valueFormatter: (val) => val < 0 ? 0 : val, |
| | | valueFormatter: (val) => formatQuantity(val != null && val >= 0 ? val : 0), |
| | | }, |
| | | { |
| | | field: 'splrCode', |
| | |
| | | import DictSelect from "../../components/DictSelect"; |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | |
| | | const TransferCreate = (props) => { |
| | | const { open, setOpen, orderId } = props; |
| | |
| | | minWidth: 100, |
| | | flex: 1, |
| | | editable: true, |
| | | valueFormatter: (val) => val < 0 ? 0 : val, |
| | | valueFormatter: (val) => formatQuantity(val != null && val >= 0 ? val : 0), |
| | | headerClassName: "custom", |
| | | }, |
| | | { |
| | |
| | | import { useForm, Controller, useWatch, FormProvider, useFormContext } from "react-hook-form"; |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import { Add, Edit, Delete } from '@mui/icons-material'; |
| | | import { DataGrid, useGridApiRef, GRID_DATE_COL_DEF, GRID_DATETIME_COL_DEF, getGridDateOperators, useGridApiContext } from '@mui/x-data-grid'; |
| | | import { LocalizationProvider, DatePicker, DateTimePicker } from '@mui/x-date-pickers'; |
| | |
| | | minWidth: 100, |
| | | flex: 1, |
| | | editable: false, |
| | | valueFormatter: (val) => val < 0 ? 0 : val, |
| | | valueFormatter: (val) => formatQuantity(val != null && val >= 0 ? val : 0), |
| | | }, |
| | | { |
| | | field: 'workQty', |
| | |
| | | type: 'number', |
| | | minWidth: 100, |
| | | flex: 1, |
| | | valueFormatter: (val) => val < 0 ? 0 : val, |
| | | valueFormatter: (val) => formatQuantity(val != null && val >= 0 ? val : 0), |
| | | }, |
| | | { |
| | | field: 'unit', |
| | |
| | | Box, |
| | | } from '@mui/material'; |
| | | import DialogCloseButton from "../components/DialogCloseButton"; |
| | | import QuantityInput from "../components/QuantityInput"; |
| | | import StatusSelectInput from "../components/StatusSelectInput"; |
| | | import MemoInput from "../components/MemoInput"; |
| | | |
| | |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.stockItem.anfme" |
| | | source="anfme" |
| | | validate={required()} |
| | | validate={[required()]} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.stockItem.workQty" |
| | | source="workQty" |
| | | validate={required()} |
| | | validate={[required()]} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={6} display="flex" gap={1}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.stockItem.qty" |
| | | source="qty" |
| | | /> |
| | |
| | | import CustomerTopToolBar from "../components/EditTopToolBar"; |
| | | import MemoInput from "../components/MemoInput"; |
| | | import StatusSelectInput from "../components/StatusSelectInput"; |
| | | import QuantityInput from "../components/QuantityInput"; |
| | | |
| | | const FormToolbar = () => { |
| | | const { getValues } = useFormContext(); |
| | |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.stockItem.anfme" |
| | | source="anfme" |
| | | validate={required()} |
| | | validate={[required()]} |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.stockItem.workQty" |
| | | source="workQty" |
| | | validate={required()} |
| | | validate={[required()]} |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | |
| | | /> |
| | | </Stack> |
| | | <Stack direction='row' gap={2}> |
| | | <NumberInput |
| | | <QuantityInput |
| | | label="table.field.stockItem.qty" |
| | | source="qty" |
| | | /> |
| | |
| | | import SelectMatnrInfo from "./SelectMatnrInfo"; |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity, hasMoreThan6Decimals } from '@/utils/common'; |
| | | import "./asnOrder.css"; |
| | | |
| | | const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({ |
| | |
| | | } |
| | | })); |
| | | |
| | | if (value.some((el) => hasMoreThan6Decimals(el.anfme) || hasMoreThan6Decimals(el.reviseQty))) { |
| | | notify('最多6位小数'); |
| | | return; |
| | | } |
| | | saveReviseLog(value); |
| | | }; |
| | | |
| | |
| | | minWidth: 100, |
| | | flex: 1, |
| | | editable: false, |
| | | valueFormatter: (v) => formatQuantity(v), |
| | | }, |
| | | { |
| | | field: 'reviseQty', |
| | |
| | | flex: 1, |
| | | editable: true, |
| | | headerClassName: "custom", |
| | | valueFormatter: (v) => formatQuantity(v), |
| | | }, |
| | | { |
| | | field: 'batch', |
| | |
| | | <TextField source="matnrCode" label="table.field.locItem.matnrCode" /> |
| | | <TextField source="maktx" label="table.field.locItem.maktx" /> |
| | | <TextField source="unit" label="table.field.locItem.unit" /> |
| | | <NumberField source="anfme" label="table.field.locItem.anfme" /> |
| | | <NumberField source="reviseQty" label="table.field.locItem.reviseQty" /> |
| | | <NumberField source="anfme" label="table.field.locItem.anfme" options={{ maximumFractionDigits: 6 }} /> |
| | | <NumberField source="reviseQty" label="table.field.locItem.reviseQty" options={{ maximumFractionDigits: 6 }} /> |
| | | <CheckDiffField source="diffQty" label="table.field.locItem.diffQty" /> |
| | | <TextField source="batch" label="table.field.locItem.batch" /> |
| | | <TextField source="spec" label="table.field.locItem.spec" /> |
| | |
| | | <TextField source="maktx" label="table.field.taskItem.maktx" />, |
| | | <TextField source="matnrCode" label="table.field.taskItem.matnrCode" />, |
| | | <TextField source="unit" label="table.field.taskItem.unit" />, |
| | | <NumberField source="anfme" label="table.field.taskItem.anfme" />, |
| | | <NumberField source="qty" label="table.field.taskItem.qty" />, |
| | | <NumberField source="anfme" label="table.field.taskItem.anfme" options={{ maximumFractionDigits: 6 }} />, |
| | | <NumberField source="qty" label="table.field.taskItem.qty" options={{ maximumFractionDigits: 6 }} />, |
| | | <TextField source="platOrderCode" label="table.field.asnOrderItem.platOrderCode" />, |
| | | <TextField source="platWorkCode" label="table.field.asnOrderItem.platWorkCode" />, |
| | | <TextField source="projectCode" label="table.field.asnOrderItem.projectCode" />, |
| | |
| | | <TextField source="maktx" label="table.field.taskItem.maktx" /> |
| | | <TextField source="matnrCode" label="table.field.taskItem.matnrCode" /> |
| | | <TextField source="unit" label="table.field.taskItem.unit" /> |
| | | <NumberField source="anfme" label="table.field.taskItem.anfme" /> |
| | | <NumberField source="anfme" label="table.field.taskItem.anfme" options={{ maximumFractionDigits: 6 }} /> |
| | | <TextField source="batch" label="table.field.taskItem.batch" /> |
| | | <TextField source="spec" label="table.field.taskItem.spec" /> |
| | | <TextField source="model" label="table.field.taskItem.model" /> |
| | |
| | | import AddIcon from '@mui/icons-material/Add'; |
| | | import DeleteIcon from '@mui/icons-material/Delete'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import LocItemInfoModal from "../components/locItemInfoModal"; |
| | | import { Delete } from '@mui/icons-material'; |
| | | import _, { set } from 'lodash'; |
| | |
| | | type: 'number', |
| | | width: 100, |
| | | editable: false, |
| | | valueFormatter: (v) => formatQuantity(v), |
| | | }, |
| | | // { |
| | | // field: 'workQty', |
| | |
| | | import DialogCloseButton from "../../components/DialogCloseButton"; |
| | | import { useTranslate, useNotify, useRefresh } from 'react-admin'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import { DataGrid } from '@mui/x-data-grid'; |
| | | import SaveIcon from '@mui/icons-material/Save'; |
| | | import TreeSelectInput from "@/page/components/TreeSelectInput"; |
| | |
| | | { field: 'matnrCode', headerName: translate('table.field.locItem.matnrCode'), width: 200 }, |
| | | { field: 'maktx', headerName: translate('table.field.locItem.maktx'), width: 300 }, |
| | | { field: 'batch', headerName: translate('table.field.locItem.batch'), width: 100 }, |
| | | { field: 'anfme', headerName: translate('table.field.locItem.anfme'), width: 100 }, |
| | | { field: 'anfme', headerName: translate('table.field.locItem.anfme'), width: 100, valueFormatter: (v) => formatQuantity(v) }, |
| | | { field: 'unit', headerName: translate('table.field.locItem.unit'), width: 100 }, |
| | | ]) |
| | | |
| | |
| | | import AddIcon from '@mui/icons-material/Add'; |
| | | import DeleteIcon from '@mui/icons-material/Delete'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import LocItemInfoModal from "../components/locItemInfoModal"; |
| | | import { Delete } from '@mui/icons-material'; |
| | | import StaSelect from "../components/StaSelect"; |
| | |
| | | width: 100, |
| | | type: 'number', |
| | | editable: true, |
| | | headerClassName: "custom", |
| | | |
| | | headerClassName: "custom", |
| | | valueFormatter: (v) => formatQuantity(v), |
| | | }, |
| | | { |
| | | field: 'anfme', |
| | |
| | | type: 'number', |
| | | width: 100, |
| | | editable: false, |
| | | valueFormatter: (v) => formatQuantity(v), |
| | | }, |
| | | // { |
| | | // field: 'workQty', |
| | |
| | | import AddIcon from '@mui/icons-material/Add'; |
| | | import DeleteIcon from '@mui/icons-material/Delete'; |
| | | import request from '@/utils/request'; |
| | | import { formatQuantity } from '@/utils/common'; |
| | | import LocItemInfoModal from "../components/locItemInfoModal"; |
| | | import { Delete } from '@mui/icons-material'; |
| | | import _, { set } from 'lodash'; |
| | |
| | | type: 'number', |
| | | width: 100, |
| | | editable: false, |
| | | valueFormatter: (v) => formatQuantity(v), |
| | | }, |
| | | // { |
| | | // field: 'workQty', |
| | |
| | | |
| | | /** 库存/数量显示:保留最多6位小数,去掉末尾多余的0(不强制补零) */ |
| | | export const formatQuantity = (value) => { |
| | | if (value == null || value === '') return '0'; |
| | | const n = Number(value); |
| | | if (Number.isNaN(n)) return '0'; |
| | | if (n < 0) return '0'; |
| | | return n % 1 === 0 ? String(n) : n.toFixed(6).replace(/\.?0+$/, ''); |
| | | }; |
| | | |
| | | /** 校验最多 N 位小数,用于数量类字段;超过时返回错误信息并阻止提交 */ |
| | | export const maxDecimalPlaces = (maxDecimals, message) => { |
| | | const factor = Math.pow(10, maxDecimals); |
| | | const msg = message || `最多${maxDecimals}位小数`; |
| | | return (value) => { |
| | | if (value == null || value === '') return undefined; |
| | | const n = Number(value); |
| | | if (Number.isNaN(n)) return undefined; |
| | | const rounded = Math.round(n * factor) / factor; |
| | | if (Math.abs(n - rounded) > 1e-10) return msg; |
| | | return undefined; |
| | | }; |
| | | }; |
| | | |
| | | /** 判断数值是否超过 6 位小数(用于提交前校验) */ |
| | | export const hasMoreThan6Decimals = (value) => { |
| | | if (value == null || value === '') return false; |
| | | const n = Number(value); |
| | | if (Number.isNaN(n)) return false; |
| | | const rounded = Math.round(n * 1e6) / 1e6; |
| | | return Math.abs(n - rounded) > 1e-10; |
| | | }; |
| | | |
| | | export const extractNavMenus = (data) => { |
| | | if (!data) { |
| | | return; |
| | |
| | | |
| | | @Data |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "ReportParams", description = "希日ERP上报参数") |
| | | @ApiModel(value = "ReportParams", description = "ERP上报参数") |
| | | public class ReportParams implements Serializable { |
| | | |
| | | @ApiModelProperty("订单类型") |
| | |
| | | |
| | | @Data |
| | | @Accessors(chain = true) |
| | | @ApiModel(value = "ReportParams", description = "希日ERP上报参数") |
| | | @ApiModel(value = "ReportParams", description = "ERP上报参数") |
| | | public class ReportParams implements Serializable { |
| | | |
| | | @ApiModelProperty("订单类型") |
| | |
| | | return pdaCheckOrderService.getCheckTaskItemList2(map.get("barcode")); |
| | | } |
| | | |
| | | @ApiOperation("希日无单据临时盘点") |
| | | @ApiOperation("无单据临时盘点") |
| | | @PostMapping("/check/temp/items") |
| | | public R tempCheckItem(@RequestBody Map<String, String> map) { |
| | | if (Objects.isNull(map)) { |
| | |
| | | /** |
| | | * @author Ryan |
| | | * @date 2025/11/5 |
| | | * @description: 希日无单据临时盘点 |
| | | * @description: 无单据临时盘点 |
| | | * @version 1.0 |
| | | */ |
| | | @Override |
| | |
| | | @Override |
| | | public R getOutStockTaskItem(String barcode) { |
| | | LambdaQueryWrapper<Task> lambdaQueryWrapper = new LambdaQueryWrapper<>(); |
| | | lambdaQueryWrapper.eq(Task::getBarcode, barcode); |
| | | lambdaQueryWrapper.eq(Task::getBarcode, barcode) |
| | | .orderByDesc(Task::getId) |
| | | .last("limit 1"); |
| | | Task task = taskService.getOne(lambdaQueryWrapper); |
| | | if (null == task) { |
| | | return R.error("未查询到相关任务"); |
| | |
| | | @Synchronized |
| | | public R saveOutTaskSts(String barcode) { |
| | | LambdaQueryWrapper<Task> lambdaQueryWrapper = new LambdaQueryWrapper<>(); |
| | | lambdaQueryWrapper.eq(Task::getBarcode, barcode); |
| | | lambdaQueryWrapper.eq(Task::getBarcode, barcode) |
| | | .orderByDesc(Task::getId) |
| | | .last("limit 1"); |
| | | Task task = taskService.getOne(lambdaQueryWrapper); |
| | | if (null == task) { |
| | | throw new CoolException("未找到容器号对应任务"); |
| | | } |
| | | if (!task.getTaskStatus().equals(TaskStsType.WAVE_SEED.id)) { |
| | | // 允许 199(WAVE_SEED 播种中/待确认)或 196(AWAIT 等待确认),与盘点 PDA 逻辑一致 |
| | | if (!task.getTaskStatus().equals(TaskStsType.WAVE_SEED.id) |
| | | && !task.getTaskStatus().equals(TaskStsType.AWAIT.id)) { |
| | | return R.error("任务状态不是等待确认"); |
| | | } |
| | | |
| | |
| | | if (Cools.isEmpty(barcode)) { |
| | | throw new CoolException("参数有误"); |
| | | } |
| | | Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, barcode)); |
| | | Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, barcode) |
| | | .orderByDesc(Task::getId) |
| | | .last("limit 1")); |
| | | if (null == task) { |
| | | throw new CoolException("未找到容器号对应任务"); |
| | | } |
| | |
| | | if (Objects.isNull(param.get("orderId"))) { |
| | | return R.error("订单ID不能为空!!"); |
| | | } |
| | | Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, param.get("barcode").toString())); |
| | | Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, param.get("barcode").toString()) |
| | | .orderByDesc(Task::getId) |
| | | .last("limit 1")); |
| | | if (Objects.isNull(task)) { |
| | | throw new CoolException("数据错误,任务档已不存在!!"); |
| | | } |
| | |
| | | if (Objects.isNull(params.getTaskItems()) || params.getTaskItems().isEmpty()) { |
| | | return R.error("拣货明细不能为空!"); |
| | | } |
| | | Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, params.getBarcode())); |
| | | Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, params.getBarcode()) |
| | | .orderByDesc(Task::getId) |
| | | .last("limit 1")); |
| | | if (null == task) { |
| | | return R.error("未找到托盘对应的任务"); |
| | | } |
| | |
| | | return R.error("数据错误!!"); |
| | | } |
| | | |
| | | Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, containerWaveParam.getContainer())); |
| | | Task task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getBarcode, containerWaveParam.getContainer()) |
| | | .orderByDesc(Task::getId) |
| | | .last("limit 1")); |
| | | if (null == task) { |
| | | return R.error("未找到托盘对应的任务"); |
| | | } |
| New file |
| | |
| | | package com.vincent.rsf.server.common.utils; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | |
| | | /** |
| | | * 数量运算工具:使用 BigDecimal 避免 Double 精度问题。 |
| | | * 实体仍为 Double 时,运算用本工具再转回 Double(保留 6 位小数)。 |
| | | */ |
| | | public final class QuantityUtils { |
| | | |
| | | private static final int SCALE = 6; |
| | | private static final RoundingMode ROUNDING = RoundingMode.HALF_UP; |
| | | |
| | | private QuantityUtils() { |
| | | } |
| | | |
| | | private static BigDecimal toBigDecimal(Double v) { |
| | | if (v == null) { |
| | | return BigDecimal.ZERO; |
| | | } |
| | | return BigDecimal.valueOf(v); |
| | | } |
| | | |
| | | /** |
| | | * 加法:a + b,结果保留 6 位小数转 Double |
| | | */ |
| | | public static Double add(Double a, Double b) { |
| | | return toBigDecimal(a).add(toBigDecimal(b)).setScale(SCALE, ROUNDING).doubleValue(); |
| | | } |
| | | |
| | | /** |
| | | * 减法:a - b,结果保留 6 位小数转 Double |
| | | */ |
| | | public static Double subtract(Double a, Double b) { |
| | | return toBigDecimal(a).subtract(toBigDecimal(b)).setScale(SCALE, ROUNDING).doubleValue(); |
| | | } |
| | | |
| | | /** |
| | | * 比较:a 与 b 大小,避免直接用 Double 比较。 |
| | | * 返回 a.compareTo(b):负/0/正 |
| | | */ |
| | | public static int compare(Double a, Double b) { |
| | | return toBigDecimal(a).compareTo(toBigDecimal(b)); |
| | | } |
| | | |
| | | /** |
| | | * a > 0 |
| | | */ |
| | | public static boolean isPositive(Double a) { |
| | | return compare(a, 0.0) > 0; |
| | | } |
| | | |
| | | /** |
| | | * a >= 0 |
| | | */ |
| | | public static boolean isNonNegative(Double a) { |
| | | return compare(a, 0.0) >= 0; |
| | | } |
| | | |
| | | /** |
| | | * a <= 0 |
| | | */ |
| | | public static boolean isNonPositive(Double a) { |
| | | return compare(a, 0.0) <= 0; |
| | | } |
| | | |
| | | /** |
| | | * 统一舍入到 6 位小数后转 Double(用于存库/展示,避免浮点尾差) |
| | | */ |
| | | public static Double roundToScale(Double v) { |
| | | if (v == null) { |
| | | return 0.0; |
| | | } |
| | | return toBigDecimal(v).setScale(SCALE, ROUNDING).doubleValue(); |
| | | } |
| | | } |
| | |
| | | ORDER_WORK_TYPE_SALE("4", "销售退回入库单"), |
| | | ORDER_WORK_TYPE_OTHER_IN("5", "其它入库单"), |
| | | ORDER_WORK_TYPE_OTHER_TERANSFER_IN("6", "调拔入库单"), |
| | | ORDER_WORK_TYPE_OTHER_TERANSFER("8", "调拔入库单"),//希日项目临时修改 |
| | | ORDER_WORK_TYPE_OTHER_TERANSFER("8", "调拔入库单"),//项目临时修改 |
| | | ORDER_WORK_TYPE_STOCK_REVISE("7", "库存调整单"), |
| | | ORDER_WORK_TYPE_SUPPLIER("11", "销售出库单"), |
| | | ORDER_WORK_TYPE_RETURN_ORDER("12", "领料出库单"), |
| | |
| | | * @param locUseStatus 库位状态,为空则不过滤 |
| | | * @return 每行: matnrId, stockQty, locStatuses(逗号分隔的库位状态,仅当 locUseStatus 为空时返回) |
| | | */ |
| | | List<Map<String, Object>> listStockByMatnrIds(@Param("matnrIds") List<Long> matnrIds, @Param("locUseStatus") String locUseStatus); |
| | | /** |
| | | * 按物料+状态+库位分组:每行 (matnrId, useStatus, locCode, locQty)。 |
| | | * 汇总 stockQty 与拼接 "库位(数量)" 在 Java 中完成,减轻数据库压力。 |
| | | */ |
| | | List<Map<String, Object>> listStockByMatnrIdsGroupByStatusAndLoc(@Param("matnrIds") List<Long> matnrIds, @Param("locUseStatus") String locUseStatus); |
| | | } |
| | |
| | | |
| | | WarehouseAreasItem warehousItem = warehouseAreasItemService.getOne(new LambdaQueryWrapper<WarehouseAreasItem>().eq(StringUtils.isNotBlank(orderItem.getFieldsIndex()), WarehouseAreasItem::getFieldsIndex, orderItem.getFieldsIndex())); |
| | | if (!Objects.isNull(warehousItem)) { |
| | | //希日相同票号,收货区不可新增相同票号数据 |
| | | //相同票号,收货区不可新增相同票号数据 |
| | | FieldsItem fieldsItem = fieldsItemService.getOne(new LambdaQueryWrapper<FieldsItem>() |
| | | .eq(FieldsItem::getUuid, orderItem.getFieldsIndex()).last("LIMIT 1")); |
| | | if (!Objects.isNull(fieldsItem)) { |
| | |
| | | @Synchronized |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public synchronized void generateTask(Short resouce, LocToTaskParams map, Long loginUserId) throws Exception { |
| | | if (Objects.isNull(map.getSiteNo())) { |
| | | throw new CoolException("站点不能为空!"); |
| | | } |
| | | // 出库口未传时默认 1001 |
| | | String siteNo = StringUtils.isNotBlank(map.getSiteNo()) ? map.getSiteNo() : "1001"; |
| | | if (Objects.isNull(map.getItems()) || map.getItems().isEmpty()) { |
| | | throw new CoolException("明细不能为空!"); |
| | | } |
| | | String siteNo = map.getSiteNo(); |
| | | List<LocItem> items = map.getItems(); |
| | | Map<Long, List<LocItem>> listMap = items.stream().collect(Collectors.groupingBy(LocItem::getLocId)); |
| | | WkOrder order; |
| | |
| | | |
| | | @Override |
| | | public PageParam<Matnr, BaseParam> pageMatnrForOutStock(PageParam<Matnr, BaseParam> pageParam, Map<String, Object> params) { |
| | | // 在 getMatnrPage 之前取出 locUseStatus:getMatnrPage 会从 where.map 中 remove 掉该键(与 params 同引用),导致后续取不到 |
| | | String locUseStatus = params.get("locUseStatus") != null ? params.get("locUseStatus").toString().trim() : null; |
| | | if (locUseStatus != null && locUseStatus.isEmpty()) locUseStatus = null; |
| | | |
| | | PageParam<Matnr, BaseParam> page = matnrService.getMatnrPage(pageParam, params); |
| | | List<Matnr> records = page.getRecords(); |
| | | if (records == null || records.isEmpty()) { |
| | | return page; |
| | | } |
| | | List<Long> matnrIds = records.stream().map(Matnr::getId).collect(Collectors.toList()); |
| | | String locUseStatus = params.get("locUseStatus") != null ? params.get("locUseStatus").toString() : null; |
| | | List<Map<String, Object>> stockList = locItemMapper.listStockByMatnrIds(matnrIds, locUseStatus); |
| | | Map<Long, Double> stockQtyMap = new HashMap<>(); |
| | | Map<Long, String> locStatusDescMap = new HashMap<>(); |
| | | Map<Long, String> locCodesMap = new HashMap<>(); |
| | | for (Map<String, Object> row : stockList) { |
| | | Long matnrId = getLong(row, "matnrId", "matnrid"); |
| | | if (matnrId == null) continue; |
| | | Object qty = getAny(row, "stockQty", "stockqty"); |
| | | double v = qty instanceof Number ? ((Number) qty).doubleValue() : 0d; |
| | | stockQtyMap.put(matnrId, v); |
| | | String locCodes = getStr(row, "locCodes", "loccodes"); |
| | | if (locCodes != null && !locCodes.isEmpty()) { |
| | | locCodesMap.put(matnrId, locCodes); |
| | | } |
| | | String locStatuses = getStr(row, "locStatuses", "locstatuses"); |
| | | if (locStatuses != null && !locStatuses.isEmpty()) { |
| | | String desc = Arrays.stream(locStatuses.split(",")) |
| | | .map(String::trim) |
| | | .map(LocStsType::getDescByType) |
| | | .collect(Collectors.joining(",")); |
| | | locStatusDescMap.put(matnrId, desc); |
| | | } else if (locUseStatus != null && !locUseStatus.isEmpty()) { |
| | | locStatusDescMap.put(matnrId, LocStsType.getDescByType(locUseStatus)); |
| | | } |
| | | } |
| | | List<Map<String, Object>> stockByLocList = locItemMapper.listStockByMatnrIdsGroupByStatusAndLoc(matnrIds, locUseStatus); |
| | | Map<Long, List<Map<String, Object>>> rowsByMatnr = buildRowsByMatnrFromPerLoc(stockByLocList); |
| | | List<Matnr> expanded = new ArrayList<>(); |
| | | for (Matnr record : records) { |
| | | record.setStockQty(stockQtyMap.getOrDefault(record.getId(), 0d)); |
| | | record.setLocUseStatus$(locStatusDescMap.get(record.getId())); |
| | | record.setLocCodes$(locCodesMap.get(record.getId())); |
| | | List<Map<String, Object>> statusRows = rowsByMatnr.get(record.getId()); |
| | | if (statusRows == null || statusRows.isEmpty()) { |
| | | record.setStockQty(0d); |
| | | record.setLocUseStatus$(null); |
| | | record.setLocCodes$(null); |
| | | expanded.add(record); |
| | | continue; |
| | | } |
| | | for (Map<String, Object> row : statusRows) { |
| | | double v = row.get("stockQty") instanceof Number ? ((Number) row.get("stockQty")).doubleValue() : 0d; |
| | | String useStatus = getStr(row, "useStatus", "usestatus"); |
| | | String statusDesc = useStatus != null ? LocStsType.getDescByType(useStatus) : null; |
| | | String locCodesWithQty = getStr(row, "locCodes$", "loccodes$"); |
| | | Matnr copy = cloneMatnrForRow(record); |
| | | copy.setStockQty(v); |
| | | copy.setLocUseStatus$(statusDesc); |
| | | copy.setLocCodes$(locCodesWithQty); |
| | | expanded.add(copy); |
| | | } |
| | | } |
| | | page.setRecords(expanded); |
| | | return page; |
| | | } |
| | | |
| | | /** 复制物料用于按状态展开行(仅复制展示用字段,id 保持原样供前端选行用) */ |
| | | private static Matnr cloneMatnrForRow(Matnr source) { |
| | | Matnr copy = new Matnr(); |
| | | BeanUtils.copyProperties(source, copy, "stockQty", "locUseStatus$", "locCodes$"); |
| | | return copy; |
| | | } |
| | | |
| | | /** |
| | | * 仅用「按库位明细」查询结果在内存中分组汇总:按 (matnrId, useStatus) 聚合, |
| | | * 得到 stockQty、locCodes$(库位(数量),...),减轻数据库压力。 |
| | | */ |
| | | private static Map<Long, List<Map<String, Object>>> buildRowsByMatnrFromPerLoc(List<Map<String, Object>> stockByLocList) { |
| | | Map<String, List<Map<String, Object>>> perLocByMatnrAndStatus = new HashMap<>(); |
| | | for (Map<String, Object> locRow : stockByLocList) { |
| | | Long mid = getLong(locRow, "matnrId", "matnrid"); |
| | | String us = getStr(locRow, "useStatus", "usestatus"); |
| | | if (mid == null || us == null) continue; |
| | | String key = mid + ":" + us; |
| | | perLocByMatnrAndStatus.computeIfAbsent(key, k -> new ArrayList<>()).add(locRow); |
| | | } |
| | | Map<Long, List<Map<String, Object>>> rowsByMatnr = new HashMap<>(); |
| | | for (Map.Entry<String, List<Map<String, Object>>> e : perLocByMatnrAndStatus.entrySet()) { |
| | | String[] parts = e.getKey().split(":", 2); |
| | | if (parts.length != 2) continue; |
| | | Long matnrId; |
| | | try { matnrId = Long.parseLong(parts[0]); } catch (NumberFormatException ex) { continue; } |
| | | String useStatus = parts[1]; |
| | | List<Map<String, Object>> locs = e.getValue(); |
| | | double stockQty = 0d; |
| | | StringBuilder sb = new StringBuilder(); |
| | | for (Map<String, Object> locRow : locs) { |
| | | Object q = getAny(locRow, "locQty", "locqty"); |
| | | double qty = q instanceof Number ? ((Number) q).doubleValue() : 0d; |
| | | stockQty += qty; |
| | | String code = getStr(locRow, "locCode", "loccode"); |
| | | if (sb.length() > 0) sb.append(","); |
| | | sb.append(code != null ? code : "").append("(").append(formatQtyForLoc(qty)).append(")"); |
| | | } |
| | | Map<String, Object> statusRow = new HashMap<>(); |
| | | statusRow.put("matnrId", matnrId); |
| | | statusRow.put("useStatus", useStatus); |
| | | statusRow.put("stockQty", stockQty); |
| | | statusRow.put("locCodes$", sb.toString()); |
| | | rowsByMatnr.computeIfAbsent(matnrId, k -> new ArrayList<>()).add(statusRow); |
| | | } |
| | | return rowsByMatnr; |
| | | } |
| | | |
| | | private static String formatQtyForLoc(double qty) { |
| | | if (qty == (long) qty) return String.valueOf((long) qty); |
| | | String s = BigDecimal.valueOf(qty).setScale(6, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString(); |
| | | return s; |
| | | } |
| | | |
| | | private static Long getLong(Map<String, Object> map, String... keys) { |
| | |
| | | locItems.add(locItem); |
| | | |
| | | LocToTaskParams taskParams = new LocToTaskParams(); |
| | | // 出库单下发任务时,出库口未传则默认 1001 |
| | | String siteNo = StringUtils.isNotBlank(param.getSiteNo()) ? param.getSiteNo() : "1001"; |
| | | taskParams.setType(Constants.TASK_TYPE_ORDER_OUT_STOCK) |
| | | .setOrgLoc(loc.getCode()) |
| | | .setItems(locItems) |
| | | .setSourceId(outId) |
| | | .setSiteNo(param.getSiteNo()); |
| | | .setSiteNo(siteNo); |
| | | try { |
| | | //生成出库任务 |
| | | locItemService.generateTask(TaskResouceType.TASK_RESOUCE_ORDER_TYPE.val, taskParams, loginUserId); |
| | |
| | | .eq(DeviceSite::getChannel, loc.getChannel()) |
| | | .eq(DeviceSite::getType, issued.doubleValue() >= locItem.getAnfme() && itemList.size() == 1 ? TaskType.TASK_TYPE_OUT.type : TaskType.TASK_TYPE_PICK_AGAIN_OUT.type) |
| | | ); |
| | | // 出库口列表排序:1001 排第一,作为默认 |
| | | deviceSites.sort((a, b) -> { |
| | | boolean a1001 = "1001".equals(a.getSite()); |
| | | boolean b1001 = "1001".equals(b.getSite()); |
| | | if (a1001 && !b1001) return -1; |
| | | if (!a1001 && b1001) return 1; |
| | | return 0; |
| | | }); |
| | | |
| | | if (!deviceSites.isEmpty()) { |
| | | List<OrderOutItemDto.staListDto> maps = new ArrayList<>(); |
| | |
| | | orderOutItemDto.setStaNos(maps); |
| | | //默认获取第一站点 |
| | | DeviceSite deviceSite = deviceSites.stream().findFirst().get(); |
| | | orderOutItemDto.setSiteNo(deviceSite.getSite()); |
| | | orderOutItemDto.setSitesNo(deviceSite.getSite()); |
| | | } |
| | | |
| | | list.add(orderOutItemDto); |
| | |
| | | import com.vincent.rsf.server.api.entity.params.WcsTaskParams; |
| | | import com.vincent.rsf.server.api.service.WcsService; |
| | | import com.vincent.rsf.server.common.constant.Constants; |
| | | import com.vincent.rsf.server.common.utils.QuantityUtils; |
| | | import com.vincent.rsf.server.manager.controller.params.LocToTaskParams; |
| | | import com.vincent.rsf.server.manager.controller.params.PakinItem; |
| | | import com.vincent.rsf.server.manager.enums.*; |
| | |
| | | throw new CoolException("任务明细不存在!!"); |
| | | } |
| | | |
| | | List<LocItem> items = new ArrayList<>(); |
| | | for (TaskItem taskItem : taskItems) { |
| | | LocItem locItem = new LocItem(); |
| | | LocItemWorking locWorking = locItemWorkingService.getOne(new LambdaQueryWrapper<LocItemWorking>() |
| | | .eq(LocItemWorking::getTaskId, taskItem.getTaskId()) |
| | | .eq(StringUtils.isNotBlank(taskItem.getFieldsIndex()), LocItemWorking::getFieldsIndex, taskItem.getFieldsIndex()) |
| | | .eq(StringUtils.isNotEmpty(taskItem.getBatch()), LocItemWorking::getBatch, taskItem.getBatch()) |
| | | .eq(LocItemWorking::getMatnrId, taskItem.getMatnrId())); |
| | | if (Objects.isNull(locWorking)) { |
| | | continue; |
| | | if (TaskType.TASK_TYPE_PICK_IN.type.equals(task.getTaskType())) { |
| | | // 拣料再入库:出库时已在 pickOrCheckTask 中扣减原库位(1100 -> 1089.899), |
| | | // 入库完成时不再回写/累加库位明细,保持 1 条 1089.899,避免出现两条 1100 |
| | | // 仅更新库位状态、清理 LocItemWorking、任务状态及流水 |
| | | } else { |
| | | // 盘点入库等:沿用原逻辑,从 LocItemWorking 回写并 saveBatch |
| | | List<LocItem> items = new ArrayList<>(); |
| | | for (TaskItem taskItem : taskItems) { |
| | | LocItem locItem = new LocItem(); |
| | | LocItemWorking locWorking = locItemWorkingService.getOne(new LambdaQueryWrapper<LocItemWorking>() |
| | | .eq(LocItemWorking::getTaskId, taskItem.getTaskId()) |
| | | .eq(StringUtils.isNotBlank(taskItem.getFieldsIndex()), LocItemWorking::getFieldsIndex, taskItem.getFieldsIndex()) |
| | | .eq(StringUtils.isNotEmpty(taskItem.getBatch()), LocItemWorking::getBatch, taskItem.getBatch()) |
| | | .eq(LocItemWorking::getMatnrId, taskItem.getMatnrId())); |
| | | if (Objects.isNull(locWorking)) { |
| | | continue; |
| | | } |
| | | if (task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_IN.type)) { |
| | | locWorking.setAnfme(taskItem.getAnfme()); |
| | | } |
| | | BeanUtils.copyProperties(locWorking, locItem); |
| | | locItem.setWorkQty(0.0).setQty(0.0).setLocCode(loc.getCode()).setLocId(loc.getId()).setId(null).setUpdateBy(loginUserId).setUpdateTime(new Date()); |
| | | //数量为零的不入库 |
| | | if (locItem.getAnfme().compareTo(0.0) > 0) { |
| | | items.add(locItem); |
| | | } |
| | | } |
| | | if (task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_IN.type)) { |
| | | locWorking.setAnfme(taskItem.getAnfme()); |
| | | } else if (task.getTaskType().equals(TaskType.TASK_TYPE_PICK_IN.type) && taskItem.getQty() != null && taskItem.getQty().compareTo(0.0) > 0) { |
| | | // 拣料再入库:入库数量为本次拣料数量(taskItem.qty),保证与出库扣减一致 |
| | | locWorking.setAnfme(taskItem.getQty()); |
| | | } |
| | | BeanUtils.copyProperties(locWorking, locItem); |
| | | locItem.setWorkQty(0.0).setQty(0.0).setLocCode(loc.getCode()).setLocId(loc.getId()).setId(null).setUpdateBy(loginUserId).setUpdateTime(new Date()); |
| | | //数量为零的不入库 |
| | | if (locItem.getAnfme().compareTo(0.0) > 0) { |
| | | items.add(locItem); |
| | | } |
| | | } |
| | | |
| | | if (!locItemService.saveBatch(items)) { |
| | | if (!items.isEmpty() && !locItemService.saveBatch(items)) { |
| | | // throw new CoolException("作业库存回写失败!!"); |
| | | } |
| | | } |
| | | |
| | | TaskItem taskItem = taskItems.stream().findFirst().get(); |
| | |
| | | // if (Objects.isNull(locInfo)) { |
| | | // throw new CoolException("获取库位失败!!"); |
| | | // } |
| | | //希日上报物有情况,不需要获取新库位 |
| | | //上报物有情况,不需要获取新库位 |
| | | task.setTargLoc(task.getOrgLoc()) |
| | | .setOrgSite(task.getTargSite()); |
| | | |
| | |
| | | } |
| | | //获取因当前任务出库的所有物料信息 |
| | | List<LocItemWorking> tempLocs = locItemWorkingService.list(new LambdaQueryWrapper<LocItemWorking>().eq(LocItemWorking::getTaskId, task.getId())); |
| | | if (tempLocs.isEmpty()) { |
| | | throw new CoolException("数据错误,作业中库存数据丢失!!"); |
| | | } |
| | | List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>().eq(TaskItem::getTaskId, task.getId())); |
| | | if (taskItems.isEmpty()) { |
| | | throw new CoolException("数据错误:任务明细为空!!"); |
| | | } |
| | | // 与查询一致:若无作业中库存但存在任务明细,用任务明细在内存中兜底,避免“能查到却无法确认” |
| | | if (tempLocs.isEmpty()) { |
| | | tempLocs = taskItems.stream().map(ti -> { |
| | | LocItemWorking w = new LocItemWorking(); |
| | | w.setTaskId(task.getId()); |
| | | w.setFieldsIndex(ti.getFieldsIndex()); |
| | | w.setAnfme(ti.getAnfme()); |
| | | w.setMatnrId(ti.getMatnrId()); |
| | | w.setMaktx(ti.getMaktx()); |
| | | w.setMatnrCode(ti.getMatnrCode()); |
| | | w.setSpec(ti.getSpec()); |
| | | w.setBatch(ti.getBatch()); |
| | | w.setUnit(ti.getUnit()); |
| | | w.setModel(ti.getModel()); |
| | | return w; |
| | | }).collect(Collectors.toList()); |
| | | } |
| | | |
| | | // 拣料入库:在生成拣料入库单时扣减原库位数量,不依赖出库完成时库位状态为R |
| | | // 拣料入库:先算剩余数量并更新 taskItem.anfme,已拣数量 taskItem.qty = 原库位 - 剩余(保证 数量=100、已拣数量=1、库存明细=100) |
| | | if (TaskType.TASK_TYPE_PICK_IN.type.equals(type)) { |
| | | log.debug("[拣料入库] 开始处理 taskId={}, taskCode={}, orgLoc={}, tempLocs.size={}, taskItems.size={}", |
| | | task.getId(), task.getTaskCode(), task.getOrgLoc(), tempLocs.size(), taskItems.size()); |
| | | tempLocs.forEach(working -> { |
| | | taskItems.forEach(taskItem -> { |
| | | if (Objects.equals(taskItem.getFieldsIndex(), working.getFieldsIndex())) { |
| | | // 已拣数量:优先用 taskItem.qty;为 0 时从出库单明细取 执行数(workQty) 或 订单数量(anfme),避免手动完结未填 qty 导致不扣减 |
| | | Double pickedQty = taskItem.getQty() != null && QuantityUtils.isPositive(taskItem.getQty()) |
| | | ? taskItem.getQty() |
| | | : 0.0; |
| | | log.debug("[拣料入库] taskItemId={}, fieldsIndex={}, working.anfme={}, taskItem.qty={}, taskItem.orderItemId={}, pickedQty(初)={}", |
| | | taskItem.getId(), taskItem.getFieldsIndex(), working.getAnfme(), taskItem.getQty(), taskItem.getOrderItemId(), pickedQty); |
| | | if (pickedQty <= 0 && taskItem.getOrderItemId() != null) { |
| | | WkOrderItem orderItem = asnOrderItemService.getById(taskItem.getOrderItemId()); |
| | | log.debug("[拣料入库] 查出库单明细 orderItemId={}, orderItem={}, workQty={}, anfme={}", |
| | | taskItem.getOrderItemId(), orderItem != null ? "存在" : "null", |
| | | orderItem != null ? orderItem.getWorkQty() : null, orderItem != null ? orderItem.getAnfme() : null); |
| | | if (orderItem != null) { |
| | | if (orderItem.getWorkQty() != null && QuantityUtils.isPositive(orderItem.getWorkQty())) { |
| | | pickedQty = orderItem.getWorkQty(); |
| | | } else if (orderItem.getAnfme() != null && QuantityUtils.isPositive(orderItem.getAnfme())) { |
| | | pickedQty = orderItem.getAnfme(); |
| | | } |
| | | } |
| | | } |
| | | Double minQty = QuantityUtils.subtract(working.getAnfme(), pickedQty); |
| | | log.debug("[拣料入库] 计算后 pickedQty={}, minQty(剩余)={}, 将更新 taskItem.anfme={}, taskItem.qty={}", |
| | | pickedQty, minQty, minQty, pickedQty); |
| | | if (QuantityUtils.isNonNegative(minQty)) { |
| | | taskItem.setAnfme(minQty); |
| | | taskItem.setQty(pickedQty); |
| | | if (!taskItemService.updateById(taskItem)) { |
| | | throw new CoolException("任务明细修改失败!!"); |
| | | } |
| | | } else { |
| | | log.warn("[拣料入库] minQty<0 未更新 taskItem, taskItemId={}", taskItem.getId()); |
| | | } |
| | | } |
| | | }); |
| | | }); |
| | | log.debug("[拣料入库] 即将扣减库位 locId={}, locCode={}", loc.getId(), loc.getCode()); |
| | | subtractLocItemByTaskItems(loc, taskItems, SystemAuthUtils.getLoginUserId()); |
| | | } |
| | | |
| | |
| | | if (Objects.equals(taskItem.getFieldsIndex(), working.getFieldsIndex())) { |
| | | Double minQty = taskItem.getAnfme(); |
| | | if (!task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_IN.type)) { |
| | | // 计算剩余数量:从LocItemWorking中减去TaskItem的拣料数量 |
| | | minQty = Math.round((working.getAnfme() - taskItem.getQty()) * 1000000) / 1000000.0; |
| | | // 计算剩余数量 |
| | | minQty = QuantityUtils.subtract(working.getAnfme(), taskItem.getQty()); |
| | | } |
| | | if (minQty.compareTo(0.0) >= 0) { |
| | | if (QuantityUtils.isNonNegative(minQty)) { |
| | | // 更新TaskItem的剩余数量 |
| | | taskItem.setAnfme(minQty); |
| | | if (!taskItemService.updateById(taskItem)) { |
| | | throw new CoolException("任务明细修改失败!!"); |
| | | } |
| | | // 更新LocItemWorking的剩余数量(非盘点入库时需要更新) |
| | | // 更新LocItemWorking的剩余数量(非盘点入库时需要更新);仅持久化记录才写库 |
| | | if (!task.getTaskType().equals(TaskType.TASK_TYPE_CHECK_IN.type)) { |
| | | working.setAnfme(minQty); |
| | | if (!locItemWorkingService.updateById(working)) { |
| | | if (working.getId() != null && !locItemWorkingService.updateById(working)) { |
| | | throw new CoolException("作业库存数量更新失败!!"); |
| | | } |
| | | } |
| | |
| | | .setQty(0.0) |
| | | .setLocId(loc1.getId()) |
| | | .setLocCode(loc1.getCode()); |
| | | // 拣料再入库:目标库位数量应为本次拣料数量(taskItem.qty),不是原库位剩余数量(taskItem.anfme) |
| | | if (TaskType.TASK_TYPE_PICK_IN.type.equals(task.getTaskType()) && taskItem.getQty() != null && taskItem.getQty().compareTo(0.0) > 0) { |
| | | itemWorking.setAnfme(taskItem.getQty()); |
| | | // 拣料再入库:目标库位数量应为回库的剩余数量(taskItem.anfme),即原库位减掉本次拣出后的数量(如 1000 出库 10 → 990) |
| | | if (TaskType.TASK_TYPE_PICK_IN.type.equals(task.getTaskType()) && taskItem.getAnfme() != null && taskItem.getAnfme().compareTo(0.0) > 0) { |
| | | itemWorking.setAnfme(taskItem.getAnfme()); |
| | | } |
| | | workings.add(itemWorking); |
| | | }); |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void subtractLocItemByTaskItems(Loc loc, List<TaskItem> taskItems, Long loginUserId) { |
| | | for (TaskItem taskItem : taskItems) { |
| | | // 查询对应的库位明细 |
| | | LocItem locItem = locItemService.getOne(new LambdaQueryWrapper<LocItem>() |
| | | LambdaQueryWrapper<LocItem> locItemWrapper = new LambdaQueryWrapper<LocItem>() |
| | | .eq(LocItem::getLocId, loc.getId()) |
| | | .eq(LocItem::getMatnrId, taskItem.getMatnrId()) |
| | | .eq(StringUtils.isNotBlank(taskItem.getBatch()), LocItem::getBatch, taskItem.getBatch()) |
| | | .eq(StringUtils.isNotBlank(taskItem.getFieldsIndex()), LocItem::getFieldsIndex, taskItem.getFieldsIndex())); |
| | | .eq(LocItem::getMatnrId, taskItem.getMatnrId()); |
| | | if (StringUtils.isNotBlank(taskItem.getBatch())) { |
| | | locItemWrapper.eq(LocItem::getBatch, taskItem.getBatch()); |
| | | } else { |
| | | locItemWrapper.and(w -> w.isNull(LocItem::getBatch).or().eq(LocItem::getBatch, "")); |
| | | } |
| | | if (StringUtils.isNotBlank(taskItem.getFieldsIndex())) { |
| | | locItemWrapper.eq(LocItem::getFieldsIndex, taskItem.getFieldsIndex()); |
| | | } else { |
| | | locItemWrapper.and(w -> w.isNull(LocItem::getFieldsIndex).or().eq(LocItem::getFieldsIndex, "")); |
| | | } |
| | | LocItem locItem = locItemService.getOne(locItemWrapper); |
| | | |
| | | log.info("[拣料入库-扣减库位] taskItemId={}, locId={}, matnrId={}, batch={}, fieldsIndex={}, locItem={}, locItem.anfme={}, taskItem.qty={}, taskItem.anfme={}", |
| | | taskItem.getId(), loc.getId(), taskItem.getMatnrId(), taskItem.getBatch(), taskItem.getFieldsIndex(), |
| | | locItem != null ? "存在" : "null", locItem != null ? locItem.getAnfme() : null, taskItem.getQty(), taskItem.getAnfme()); |
| | | |
| | | if (Objects.nonNull(locItem)) { |
| | | // 计算扣减后的数量 |
| | | Double newAnfme = Math.round((locItem.getAnfme() - taskItem.getQty()) * 1000000) / 1000000.0; |
| | | |
| | | if (newAnfme.compareTo(0.0) <= 0) { |
| | | // 扣减量:优先用本次拣料数量 taskItem.getQty();若为 0 且 taskItem.anfme 已为剩余数量,则扣减 = 原库位 - 剩余(用 BigDecimal 避免 Double 精度问题) |
| | | Double deductQty = QuantityUtils.isPositive(taskItem.getQty()) |
| | | ? taskItem.getQty() |
| | | : (taskItem.getAnfme() != null && QuantityUtils.compare(taskItem.getAnfme(), locItem.getAnfme()) < 0 |
| | | ? QuantityUtils.subtract(locItem.getAnfme(), taskItem.getAnfme()) |
| | | : 0.0); |
| | | Double newAnfme = QuantityUtils.subtract(locItem.getAnfme(), deductQty); |
| | | log.info("[拣料入库-扣减库位] locItemId={}, deductQty={}, 原anfme={}, newAnfme={}, 操作={}", |
| | | locItem.getId(), deductQty, locItem.getAnfme(), newAnfme, QuantityUtils.isNonPositive(newAnfme) ? "删除" : "更新"); |
| | | |
| | | if (QuantityUtils.isNonPositive(newAnfme)) { |
| | | // 数量小于等于0,删除库位明细 |
| | | locItemService.removeById(locItem.getId()); |
| | | } else { |
| | |
| | | throw new CoolException("库位明细数量扣减失败!!"); |
| | | } |
| | | } |
| | | } else { |
| | | log.warn("[拣料入库-扣减库位] 未查到库位明细 locId={}, matnrId={}, batch={}, fieldsIndex={},未扣减", |
| | | loc.getId(), taskItem.getMatnrId(), taskItem.getBatch(), taskItem.getFieldsIndex()); |
| | | } |
| | | } |
| | | } |
| | |
| | | </if> |
| | | GROUP BY li.matnr_id |
| | | </select> |
| | | |
| | | <!-- 按物料+状态+库位分组:每行一个库位及其数量,汇总与拼接在 Java 中完成以减轻库压力 --> |
| | | <select id="listStockByMatnrIdsGroupByStatusAndLoc" resultType="java.util.HashMap"> |
| | | SELECT |
| | | li.matnr_id AS matnrId, |
| | | l.use_status AS useStatus, |
| | | li.loc_code AS locCode, |
| | | COALESCE(SUM(li.anfme), 0) AS locQty |
| | | FROM man_loc_item li |
| | | INNER JOIN man_loc l ON l.id = li.loc_id AND (l.deleted = 0 OR l.deleted IS NULL) |
| | | WHERE li.deleted = 0 |
| | | AND li.matnr_id IN |
| | | <foreach collection="matnrIds" item="id" open="(" separator="," close=")"> |
| | | #{id} |
| | | </foreach> |
| | | <if test="locUseStatus != null and locUseStatus != ''"> |
| | | AND l.use_status = #{locUseStatus} |
| | | </if> |
| | | GROUP BY li.matnr_id, l.use_status, li.loc_code |
| | | ORDER BY li.matnr_id, l.use_status, li.loc_code |
| | | </select> |
| | | </mapper> |