diff --git a/src/components/Dashboard/Inventory/Shipments/NewShipment.jsx b/src/components/Dashboard/Inventory/Shipments/NewShipment.jsx index ceb60c1..e34b5b6 100644 --- a/src/components/Dashboard/Inventory/Shipments/NewShipment.jsx +++ b/src/components/Dashboard/Inventory/Shipments/NewShipment.jsx @@ -2,15 +2,13 @@ import PropTypes from 'prop-types' import ObjectInfo from '../../common/ObjectInfo' import NewObjectForm from '../../common/NewObjectForm' import WizardView from '../../common/WizardView' -import { getModelProperty } from '../../../../database/ObjectModels.js' -import ObjectProperty from '../../common/ObjectProperty.jsx' const NewShipment = ({ onOk, reset, defaultValues }) => { return ( {({ handleSubmit, submitLoading, objectData, formValid }) => { const steps = [ @@ -26,25 +24,48 @@ const NewShipment = ({ onOk, reset, defaultValues }) => { required={true} objectData={objectData} visibleProperties={{ - items: false, - cost: false, - shippedDate: false, - expectedDeliveryDate: false, - actualDeliveryDate: false, - notes: false + amount: false, + taxRate: false, + taxAmount: false, + amountWithTax: false, + syncAmount: false }} /> ) }, { - title: 'Items', - key: 'items', + title: 'Pricing', + key: 'pricing', content: ( - + ) + }, + { + title: 'Optional', + key: 'optional', + content: ( + ) }, @@ -58,9 +79,13 @@ const NewShipment = ({ onOk, reset, defaultValues }) => { bordered={false} visibleProperties={{ _id: false, + _reference: false, createdAt: false, updatedAt: false, - items: false + shippedAt: false, + expectedAt: false, + deliveredAt: false, + taxRecord: false }} isEditing={false} objectData={objectData} diff --git a/src/components/Dashboard/Inventory/Shipments/ShipmentInfo.jsx b/src/components/Dashboard/Inventory/Shipments/ShipmentInfo.jsx index 2b70633..49afa9b 100644 --- a/src/components/Dashboard/Inventory/Shipments/ShipmentInfo.jsx +++ b/src/components/Dashboard/Inventory/Shipments/ShipmentInfo.jsx @@ -1,6 +1,6 @@ import { useRef, useState } from 'react' import { useLocation } from 'react-router-dom' -import { Space, Flex, Card } from 'antd' +import { Space, Flex, Card, Modal } from 'antd' import { LoadingOutlined } from '@ant-design/icons' import loglevel from 'loglevel' import config from '../../../../config.js' @@ -21,9 +21,10 @@ import ObjectTable from '../../common/ObjectTable.jsx' import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx' import DocumentPrintButton from '../../common/DocumentPrintButton.jsx' import ScrollBox from '../../common/ScrollBox.jsx' -import { getModelProperty } from '../../../../database/ObjectModels.js' -import ObjectProperty from '../../common/ObjectProperty.jsx' -import OrderItemsIcon from '../../../Icons/OrderItemIcon.jsx' +import OrderItemIcon from '../../../Icons/OrderItemIcon.jsx' +import ShipShipment from './ShipShipment.jsx' +import ReceiveShipment from './ReceiveShipment.jsx' +import CancelShipment from './CancelShipment.jsx' const log = loglevel.getLogger('ShipmentInfo') log.setLevel(config.logLevel) @@ -51,6 +52,9 @@ const ShipmentInfo = () => { objectData: {} }) + const [shipShipmentOpen, setShipShipmentOpen] = useState(false) + const [receiveShipmentOpen, setReceiveShipmentOpen] = useState(false) + const [cancelShipmentOpen, setCancelShipmentOpen] = useState(false) const actions = { reload: () => { objectFormRef?.current?.handleFetchObject?.() @@ -71,6 +75,18 @@ const ShipmentInfo = () => { delete: () => { objectFormRef?.current?.handleDelete?.() return true + }, + ship: () => { + setShipShipmentOpen(true) + return true + }, + receive: () => { + setReceiveShipmentOpen(true) + return true + }, + cancel: () => { + setCancelShipmentOpen(true) + return true } } @@ -167,22 +183,26 @@ const ShipmentInfo = () => { visibleProperties={{ items: false }} + labelWidth='175px' /> } - active={collapseState.info} + title='Shipment Order Items' + icon={} + active={collapseState.orderItems} onToggle={(expanded) => - updateCollapseState('info', expanded) + updateCollapseState('orderItems', expanded) } - collapseKey='info' + collapseKey='orderItems' > - @@ -222,6 +242,60 @@ const ShipmentInfo = () => { + { + setShipShipmentOpen(false) + }} + width={515} + footer={null} + destroyOnHidden={true} + centered={true} + > + { + setShipShipmentOpen(false) + actions.reload() + }} + objectData={objectFormState.objectData} + /> + + { + setReceiveShipmentOpen(false) + }} + width={515} + footer={null} + destroyOnHidden={true} + centered={true} + > + { + setReceiveShipmentOpen(false) + actions.reload() + }} + objectData={objectFormState.objectData} + /> + + { + setCancelShipmentOpen(false) + }} + width={515} + footer={null} + destroyOnHidden={true} + centered={true} + > + { + setCancelShipmentOpen(false) + actions.reload() + }} + objectData={objectFormState.objectData} + /> + ) } diff --git a/src/database/models/Shipment.js b/src/database/models/Shipment.js index e5d22ed..1f7f21e 100644 --- a/src/database/models/Shipment.js +++ b/src/database/models/Shipment.js @@ -1,10 +1,14 @@ import ShipmentIcon from '../../components/Icons/ShipmentIcon' import InfoCircleIcon from '../../components/Icons/InfoCircleIcon' +import EditIcon from '../../components/Icons/EditIcon' +import BinIcon from '../../components/Icons/BinIcon' +import CheckIcon from '../../components/Icons/CheckIcon' +import XMarkIcon from '../../components/Icons/XMarkIcon' export const Shipment = { name: 'shipment', label: 'Shipment', - prefix: 'SHM', + prefix: 'SHP', icon: ShipmentIcon, actions: [ { @@ -14,25 +18,126 @@ export const Shipment = { row: true, icon: InfoCircleIcon, url: (_id) => `/dashboard/inventory/shipments/info?shipmentId=${_id}` + }, + { + name: 'edit', + label: 'Edit', + type: 'button', + icon: EditIcon, + url: (_id) => + `/dashboard/inventory/shipments/info?shipmentId=${_id}&action=edit`, + visible: (objectData) => { + return !(objectData?._isEditing && objectData?._isEditing == true) + }, + disabled: (objectData) => { + return objectData?.state?.type != 'draft' + } + }, + { + name: 'cancelEdit', + label: 'Cancel Edit', + type: 'button', + icon: XMarkIcon, + url: (_id) => + `/dashboard/inventory/shipments/info?shipmentId=${_id}&action=cancelEdit`, + visible: (objectData) => { + return objectData?._isEditing && objectData?._isEditing == true + }, + disabled: (objectData) => { + return objectData?.state?.type != 'draft' + } + }, + { + name: 'finishEdit', + label: 'Finish Edit', + type: 'button', + icon: CheckIcon, + url: (_id) => + `/dashboard/inventory/shipments/info?shipmentId=${_id}&action=finishEdit`, + visible: (objectData) => { + return objectData?._isEditing && objectData?._isEditing == true + }, + disabled: (objectData) => { + return objectData?.state?.type != 'draft' + } + }, + { + name: 'delete', + label: 'Delete', + type: 'button', + icon: BinIcon, + danger: true, + url: (_id) => + `/dashboard/inventory/shipments/info?shipmentId=${_id}&action=delete`, + visible: (objectData) => { + return !(objectData?._isEditing && objectData?._isEditing == true) + }, + disabled: (objectData) => { + return objectData?.state?.type != 'draft' + } + }, + { type: 'divider' }, + { + name: 'ship', + label: 'Ship', + type: 'button', + icon: CheckIcon, + url: (_id) => + `/dashboard/inventory/shipments/info?shipmentId=${_id}&action=ship`, + disabled: (objectData) => { + return objectData?.state?.type != 'planned' + }, + visible: (objectData) => { + return ( + objectData?.state?.type == 'planned' || + objectData?.state?.type == 'draft' + ) + } + }, + { + name: 'receive', + label: 'Receive', + type: 'button', + icon: CheckIcon, + url: (_id) => + `/dashboard/inventory/shipments/info?shipmentId=${_id}&action=receive`, + disabled: (objectData) => { + return objectData?.state?.type != 'shipped' + }, + visible: (objectData) => { + return ( + objectData?.state?.type == 'shipped' || + objectData?.state?.type == 'delivered' + ) + } } ], - group: ['vendor', 'purchaseOrder'], - filters: ['vendor', 'purchaseOrder', 'state', 'courierService'], + group: [], + filters: ['orderType', 'order', 'state', 'courierService'], sorters: [ 'createdAt', 'state', 'updatedAt', - 'shippedDate', - 'expectedDeliveryDate' + 'shippedAt', + 'expectedAt', + 'deliveredAt' ], columns: [ '_id', - 'createdAt', + '_reference', 'state', + 'orderType', + 'order', + 'amount', + 'amountWithTax', + 'taxRate', + 'taxAmount', + 'trackingNumber', + 'shippedAt', + 'expectedAt', + 'deliveredAt', 'updatedAt', - 'vendor', - 'purchaseOrder', - 'trackingNumber' + 'createdAt' ], properties: [ { @@ -50,231 +155,139 @@ export const Shipment = { type: 'dateTime', readOnly: true }, - { name: 'state', label: 'State', type: 'state', readOnly: true }, + { + name: '_reference', + label: 'Reference', + type: 'reference', + objectType: 'shipment', + showCopy: true, + readOnly: true + }, + { name: 'updatedAt', label: 'Updated At', type: 'dateTime', readOnly: true }, + { name: 'state', label: 'State', type: 'state', readOnly: true }, + { - name: 'purchaseOrder', - label: 'Purchase Order', - required: true, - type: 'object', - objectType: 'purchaseOrder', - showHyperlink: true + name: 'shippedAt', + label: 'Shipped At', + type: 'dateTime', + required: false }, { - name: 'vendor', - label: 'Vendor', + name: 'orderType', + label: 'Order Type', + required: true, + type: 'objectType', + masterFilter: ['purchaseOrder', 'salesOrder'], + showHyperlink: true + }, + + { + name: 'expectedAt', + label: 'Expected At', + type: 'dateTime', + required: false + }, + { + name: 'order', + label: 'Order', required: true, type: 'object', - objectType: 'vendor', - showHyperlink: true + showHyperlink: true, + objectType: (objectData) => { + return objectData?.orderType + } + }, + + { + name: 'deliveredAt', + label: 'Delivered At', + type: 'dateTime', + required: false }, { name: 'courierService', label: 'Courier Service', - required: false, + required: true, type: 'object', objectType: 'courierService' }, { name: 'trackingNumber', label: 'Tracking Number', - type: 'string', + type: 'text', required: false }, { - name: 'items', - label: 'Shipment Items', - type: 'objectChildren', - required: true, - properties: [ - { - name: 'itemType', - label: 'Item Type', - type: 'objectType', - masterFilter: ['part', 'packaging'], - required: true - }, - { - name: 'item', - label: 'Item', - type: 'object', - objectType: (objectData) => { - return objectData?.itemType - }, - required: true, - showHyperlink: true - }, - { - name: 'itemCost', - label: 'Item Cost', - type: 'number', - required: true, - prefix: '£', - min: 0, - step: 0.01, - columnWidth: 150, - value: (objectData) => { - if (objectData?.item) { - return objectData?.item?.cost || undefined - } else { - return undefined - } - } - }, - { - name: 'quantity', - label: 'Quantity', - type: 'number', - required: true, - columnWidth: 150 - }, - { - name: 'totalCost', - label: 'Total Cost', - type: 'number', - required: true, - prefix: '£', - min: 0, - step: 0.01, - columnWidth: 150, - value: (objectData) => { - return ( - (objectData?.itemCost || 0) * (objectData?.quantity || 0) || - undefined - ) - } - }, - { - name: 'taxRate', - label: 'Tax Rate', - type: 'object', - objectType: 'taxRate', - showHyperlink: true, - value: (objectData) => { - if (objectData?.item) { - return objectData?.item?.costTaxRate || undefined - } else { - return undefined - } - } - }, - { - name: 'totalCostWithTax', - label: 'Total Cost w/ Tax', - type: 'number', - required: true, - readOnly: true, - prefix: '£', - min: 0, - step: 0.01, - columnWidth: 175, - value: (objectData) => { - if (objectData?.taxRate?.rateType == 'percentage') { - return ( - ( - (objectData?.totalCost || 0) * - (1 + objectData?.taxRate?.rate / 100) - ).toFixed(2) || undefined - ) - } else if (objectData?.taxRate?.rateType == 'amount') { - return ( - ( - (objectData?.totalCost || 0) + objectData?.taxRate?.rate - ).toFixed(2) || undefined - ) - } else { - return objectData?.totalCost || undefined - } - } - } - ], - rollups: [ - { - name: 'totalQuantity', - label: 'Total', - type: 'number', - property: 'quantity', - value: (objectData) => { - return objectData?.items?.reduce( - (acc, item) => acc + item.quantity, - 0 - ) - } - }, - { - name: 'totalCost', - label: 'Total', - type: 'number', - prefix: '£', - property: 'totalCost', - value: (objectData) => { - return objectData?.items - ?.reduce((acc, item) => acc + (item.totalCost || 0), 0) - .toFixed(2) - } - }, - { - name: 'totalCostWithTax', - label: 'Total', - type: 'number', - prefix: '£', - property: 'totalCostWithTax', - value: (objectData) => { - return objectData?.items - ?.reduce((acc, item) => acc + (item.totalCostWithTax || 0), 0) - .toFixed(2) - } - } - ] + name: 'taxRate', + label: 'Tax Rate', + type: 'object', + objectType: 'taxRate', + showHyperlink: true }, { - name: 'cost', - label: 'Cost', - type: 'netGross', + name: 'taxRecord', + label: 'Tax Record', + type: 'object', + objectType: 'taxRecord', + showHyperlink: true + }, + { + name: 'amount', + label: 'Amount', + type: 'number', required: true, prefix: '£', min: 0, step: 0.01, + columnWidth: 150 + }, + { + name: 'taxAmount', + label: 'Tax Amount', + type: 'number', + required: true, + prefix: '£', + fixedNumber: 2, + min: 0, + step: 0.01, + readOnly: true, value: (objectData) => { - const net = objectData?.items?.reduce( - (acc, item) => acc + (item.totalCost || 0), - 0 - ) - const gross = objectData?.items?.reduce( - (acc, item) => acc + (item.totalCostWithTax || 0), - 0 - ) - return { net: net, gross: gross } + return (objectData?.amount * objectData?.taxRate?.rate) / 100 || 0 } }, { - name: 'shippedDate', - label: 'Shipped Date', - type: 'dateTime', - required: false - }, - { - name: 'expectedDeliveryDate', - label: 'Expected Delivery Date', - type: 'dateTime', - required: false - }, - { - name: 'actualDeliveryDate', - label: 'Actual Delivery Date', - type: 'dateTime', - required: false - }, - { - name: 'notes', - label: 'Notes', - type: 'textarea', - required: false + name: 'amountWithTax', + label: 'Amount w/ Tax', + type: 'number', + required: true, + prefix: '£', + min: 0, + step: 0.01, + readOnly: true, + columnWidth: 175, + value: (objectData) => { + if (objectData?._isEditing == true) { + return ( + (objectData?.amount || 0) + (objectData?.taxAmount || 0) + ).toFixed(2) + } + if ( + Number.parseFloat(objectData?.amount) && + (objectData.taxRate == undefined || objectData.taxRate == null) + ) { + return Number.parseFloat(objectData?.amount).toFixed(2) + } + if (Number.parseFloat(objectData?.amountWithTax)) { + return Number.parseFloat(objectData?.amountWithTax).toFixed(2) + } + return 0 + } } ] }