2
0
mirror of https://github.com/frappe/books.git synced 2024-12-22 19:09:01 +00:00

incr: update item docs with addItem

- start with barcode widget
This commit is contained in:
18alantom 2023-01-16 15:08:02 +05:30
parent cf4f8d368c
commit 7656476b6a
12 changed files with 202 additions and 13 deletions

View File

@ -157,6 +157,22 @@ export class Doc extends Observable<DocValue | Doc[]> {
return false;
}
get canEdit() {
if (!this.schema.isSubmittable) {
return true;
}
if (this.submitted) {
return false;
}
if (this.cancelled) {
return false;
}
return true;
}
get canSave() {
if (!!this.submitted) {
return false;

View File

@ -6,13 +6,11 @@ import {
DefaultMap,
FiltersMap,
FormulaMap,
HiddenMap
HiddenMap,
} from 'fyo/model/types';
import { DEFAULT_CURRENCY } from 'fyo/utils/consts';
import { ValidationError } from 'fyo/utils/errors';
import {
getExchangeRate, getNumberSeries
} from 'models/helpers';
import { addItem, getExchangeRate, getNumberSeries } from 'models/helpers';
import { InventorySettings } from 'models/inventory/InventorySettings';
import { StockTransfer } from 'models/inventory/StockTransfer';
import { Transactional } from 'models/Transactional/Transactional';
@ -695,4 +693,8 @@ export abstract class Invoice extends Transactional {
}))
.sort((a, b) => a.date.valueOf() - b.date.valueOf());
}
async addItem(name: string) {
return await addItem(name, this);
}
}

View File

@ -14,6 +14,8 @@ import {
numberSeriesDefaultsMap,
} from './baseModels/Defaults/Defaults';
import { Invoice } from './baseModels/Invoice/Invoice';
import { StockMovement } from './inventory/StockMovement';
import { StockTransfer } from './inventory/StockTransfer';
import { InvoiceStatus, ModelNameEnum } from './types';
export function getInvoiceActions(
@ -321,3 +323,27 @@ export function getDocStatusListColumn(): ColumnConfig {
},
};
}
type ModelsWithItems = Invoice | StockTransfer | StockMovement;
export async function addItem<M extends ModelsWithItems>(name: string, doc: M) {
if (!doc.canEdit) {
return;
}
const items = (doc.items ?? []) as NonNullable<M['items']>[number][];
let item = items.find((i) => i.item === name);
if (item) {
const q = item.quantity ?? 0;
await item.set('quantity', q + 1);
return;
}
await doc.append('items');
item = doc.items?.at(-1);
if (!item) {
return;
}
await item.set('item', name);
}

View File

@ -6,7 +6,7 @@ import {
FormulaMap,
ListViewSettings,
} from 'fyo/model/types';
import { getDocStatusListColumn, getLedgerLinkAction } from 'models/helpers';
import { addItem, getDocStatusListColumn, getLedgerLinkAction } from 'models/helpers';
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa';
@ -92,4 +92,8 @@ export class StockMovement extends Transfer {
static getActions(fyo: Fyo): Action[] {
return [getLedgerLinkAction(fyo, true)];
}
async addItem(name: string) {
return await addItem(name, this);
}
}

View File

@ -5,7 +5,7 @@ import { Action, DefaultMap, FiltersMap, FormulaMap } from 'fyo/model/types';
import { ValidationError } from 'fyo/utils/errors';
import { Defaults } from 'models/baseModels/Defaults/Defaults';
import { Invoice } from 'models/baseModels/Invoice/Invoice';
import { getLedgerLinkAction, getNumberSeries } from 'models/helpers';
import { addItem, getLedgerLinkAction, getNumberSeries } from 'models/helpers';
import { LedgerPosting } from 'models/Transactional/LedgerPosting';
import { ModelNameEnum } from 'models/types';
import { Money } from 'pesa';
@ -232,4 +232,8 @@ export abstract class StockTransfer extends Transfer {
role: doc.isSales ? 'Customer' : 'Supplier',
}),
};
async addItem(name: string) {
return await addItem(name, this);
}
}

View File

@ -0,0 +1,109 @@
<template>
<div>
<Button :icon="true" @click="openModal = true">{{
t`Scan Barcode`
}}</Button>
<Modal :open-modal="openModal" @closemodal="openModal = false">
<FormHeader :form-title="t`Barcode Scanner`" />
<hr />
<div class="p-4">
<div class="w-full flex flex-col justify-center items-center">
<input
type="text"
class="
border
w-96
rounded
text-base
px-3
py-2
placeholder-gray-600
bg-gray-50
focus-within:bg-gray-100
"
@change="(e) => getItem((e.target as HTMLInputElement)?.value)"
:placeholder="t`Enter barcode`"
/>
<div v-if="error" class="text-sm text-red-600 mt-4 w-96 text-center">
{{ error }}
</div>
<div
v-if="success"
class="text-sm text-green-600 mt-4 w-96 text-center"
>
{{ success }}
</div>
</div>
</div>
</Modal>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Button from '../Button.vue';
import FormHeader from '../FormHeader.vue';
import Modal from '../Modal.vue';
export default defineComponent({
components: { Button, Modal, FormHeader },
emits: ['item-selected'],
watch: {
openModal(value: boolean) {
if (value) {
return;
}
this.clear();
},
error(value: string) {
if (!value) {
return;
}
this.success = '';
},
success(value: string) {
if (!value) {
return;
}
this.error = '';
},
},
data() {
return {
openModal: false,
error: '',
success: '',
} as {
openModal: boolean;
error: string;
success: string;
};
},
methods: {
clear() {
this.error = '';
this.success = '';
},
async getItem(code: string) {
const barcode = code.trim();
if (!/\d{12,}/.test(barcode)) {
return (this.error = this.t`Invalid barcode ${barcode}.`);
}
const items = (await this.fyo.db.getAll('Item', {
filters: { barcode },
fields: ['name'],
})) as { name: string }[];
const name = items?.[0]?.name;
if (!name) {
return (this.error = this.t`Item with barcode ${barcode} not found.`);
}
this.success = this.t`Quantity 1 of ${name} added.`;
this.$emit('item-selected', name);
},
},
});
</script>

View File

@ -21,7 +21,6 @@
bg-white
rounded-lg
shadow-2xl
w-form
border
overflow-hidden
inner
@ -44,10 +43,6 @@ export default defineComponent({
default: false,
type: Boolean,
},
setCloseListener: {
default: true,
type: Boolean,
},
},
emits: ['closemodal'],
watch: {

View File

@ -17,7 +17,7 @@
:set-close-listener="false"
>
<!-- Search Input -->
<div class="p-1">
<div class="p-1 w-form">
<input
ref="input"
type="search"

View File

@ -167,7 +167,7 @@
<!-- Base Count Selection when Dev -->
<Modal :open-modal="openModal" @closemodal="openModal = false">
<div class="p-4 text-gray-900">
<div class="p-4 text-gray-900 w-form">
<h2 class="text-xl font-semibold select-none">Set Base Count</h2>
<p class="text-base mt-2">
Base Count is a lower bound on the number of entries made when

View File

@ -3,6 +3,10 @@
<!-- Page Header (Title, Buttons, etc) -->
<template #header v-if="doc">
<StatusBadge :status="status" />
<Barcode
v-if="showBarcode"
@item-selected="(name) => doc.addItem(name)"
/>
<DropdownWithActions
v-for="group of groupedActions"
:key="group.label"
@ -145,7 +149,9 @@
import { computed } from '@vue/reactivity';
import { t } from 'fyo';
import { getDocStatus } from 'models/helpers';
import { ModelNameEnum } from 'models/types';
import Button from 'src/components/Button.vue';
import Barcode from 'src/components/Controls/Barcode.vue';
import FormControl from 'src/components/Controls/FormControl.vue';
import Table from 'src/components/Controls/Table.vue';
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
@ -176,6 +182,7 @@ export default {
FormContainer,
QuickEditForm,
FormHeader,
Barcode,
},
provide() {
return {
@ -204,6 +211,25 @@ export default {
groupedActions() {
return getGroupedActionsForDoc(this.doc);
},
showBarcode() {
if (!this.doc) {
return false;
}
if (!this.doc.canEdit) {
return false;
}
if (!fyo.singles.InventorySettings?.enableBarcodes) {
return false;
}
return [
ModelNameEnum.Shipment,
ModelNameEnum.PurchaseReceipt,
ModelNameEnum.StockMovement,
].includes(this.schemaName);
},
},
activated() {
docsPath.value = docsPathMap[this.schemaName];

View File

@ -13,6 +13,10 @@
async (exchangeRate) => await doc.set('exchangeRate', exchangeRate)
"
/>
<Barcode
v-if="doc.canEdit && fyo.singles.InventorySettings?.enableBarcodes"
@item-selected="(name) => doc.addItem(name)"
/>
<Button
v-if="!doc.isCancelled && !doc.dirty"
:icon="true"
@ -298,6 +302,7 @@ import { computed } from '@vue/reactivity';
import { getDocStatus } from 'models/helpers';
import { ModelNameEnum } from 'models/types';
import Button from 'src/components/Button.vue';
import Barcode from 'src/components/Controls/Barcode.vue';
import ExchangeRate from 'src/components/Controls/ExchangeRate.vue';
import FormControl from 'src/components/Controls/FormControl.vue';
import Table from 'src/components/Controls/Table.vue';
@ -332,6 +337,7 @@ export default {
ExchangeRate,
FormHeader,
LinkedEntryWidget,
Barcode,
},
provide() {
return {

View File

@ -31,6 +31,7 @@
/>
<Modal :open-modal="openExportModal" @closemodal="openExportModal = false">
<ExportWizard
class="w-form"
:schema-name="schemaName"
:title="pageTitle"
:list-filters="listFilters"