mirror of
https://github.com/frappe/books.git
synced 2024-11-08 14:50:56 +00:00
Merge branch 'master' of https://github.com/slang123/frappe-books into danish-language
This commit is contained in:
commit
306f4a60b9
@ -59,5 +59,6 @@ module.exports = {
|
|||||||
'vite.config.ts',
|
'vite.config.ts',
|
||||||
'postcss.config.js',
|
'postcss.config.js',
|
||||||
'src/components/**/*.vue', // Incrementally fix these
|
'src/components/**/*.vue', // Incrementally fix these
|
||||||
|
'electron-builder.ts',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
99
.github/ISSUE_TEMPLATE/1-bug_template.yml
vendored
Normal file
99
.github/ISSUE_TEMPLATE/1-bug_template.yml
vendored
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
name: '🐛 Bug Report'
|
||||||
|
description: Create a new ticket for a bug.
|
||||||
|
title: '🐛 [Bug] - <title>'
|
||||||
|
labels: ['bug']
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: expected_behavior
|
||||||
|
attributes:
|
||||||
|
label: 'Expected Behavior'
|
||||||
|
description: What was the expected behavior?
|
||||||
|
placeholder: "..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: current_behavior
|
||||||
|
attributes:
|
||||||
|
label: 'Current Behavior'
|
||||||
|
description: What is the current behavior?
|
||||||
|
placeholder: "..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: steps_to_reproduce
|
||||||
|
attributes:
|
||||||
|
label: 'Steps to Reproduce'
|
||||||
|
description: Please try to describe the issue as best as possible
|
||||||
|
placeholder: |
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: 'FrappeBooks Version'
|
||||||
|
# description: Please enter your GitHub URL to provide a reproduction of the issue
|
||||||
|
placeholder: ex. 0.20.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: path_feature_name
|
||||||
|
attributes:
|
||||||
|
label: 'Path or Feature name'
|
||||||
|
description: Please enter the path (i.e. /import-wizard) or Feature name where the bug was seen
|
||||||
|
placeholder: ex. Import-Wizard
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: country_code
|
||||||
|
attributes:
|
||||||
|
label: 'Country'
|
||||||
|
description: Please enter the two digit country code for your country (i.e. BR, CH, IN, US)
|
||||||
|
placeholder: ex. IN
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: language
|
||||||
|
attributes:
|
||||||
|
label: 'Language'
|
||||||
|
description: Please enter the two digit language code or full lanaguage used in the application
|
||||||
|
placeholder: ex. EN or english
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: os
|
||||||
|
attributes:
|
||||||
|
label: 'OS'
|
||||||
|
description: What is the impacted environment ?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Windows 8
|
||||||
|
- Windows 8.1
|
||||||
|
- Windows 10
|
||||||
|
- Windows 11
|
||||||
|
- Linux x86_64
|
||||||
|
- Linux Arm64 (i.e. Raspberry Pi)
|
||||||
|
- Macos (Intel)
|
||||||
|
- Macos (Apple Silicon)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: additional_os_info
|
||||||
|
attributes:
|
||||||
|
label: 'Additional OS Info'
|
||||||
|
description: Please enter any additional information regarding your OS that may aid in troubleshooting (i.e. Macos 10.14, Ubuntu 20.04, etc)
|
||||||
|
placeholder: ex. Macos 10.14 / Ubuntu 20.04
|
||||||
|
validations:
|
||||||
|
required: false
|
50
.github/ISSUE_TEMPLATE/2-feature_template.yml
vendored
Normal file
50
.github/ISSUE_TEMPLATE/2-feature_template.yml
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
name: '💡 Feature Request'
|
||||||
|
description: Create a new ticket for a new feature request
|
||||||
|
title: '💡 [Feature Request] - <title>'
|
||||||
|
labels: ['enhancement']
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: summary
|
||||||
|
attributes:
|
||||||
|
label: 'Summary'
|
||||||
|
description: Provide a brief explanation of the feature
|
||||||
|
placeholder: Describe in a few lines your feature request
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: benefits
|
||||||
|
attributes:
|
||||||
|
label: 'What problem are you trying to solve?'
|
||||||
|
description: Tell us what is the thing you are doing and why this feature would help you in that
|
||||||
|
placeholder: Describe the problem or issue that the feature would solve
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: basic_example
|
||||||
|
attributes:
|
||||||
|
label: 'Basic Example'
|
||||||
|
description: Indicate here some basic examples of your feature.
|
||||||
|
placeholder: A few specific words about your feature request.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: drawbacks
|
||||||
|
attributes:
|
||||||
|
label: 'Drawbacks'
|
||||||
|
description: What are the drawbacks/impacts of your feature request ?
|
||||||
|
placeholder: Identify the drawbacks and impacts while being neutral on your feature request
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: reference_issues
|
||||||
|
attributes:
|
||||||
|
label: 'Reference Issues'
|
||||||
|
description: Common issues
|
||||||
|
placeholder: '#Issues IDs'
|
||||||
|
validations:
|
||||||
|
required: false
|
14
.github/ISSUE_TEMPLATE/3-general_question_template.yml
vendored
Normal file
14
.github/ISSUE_TEMPLATE/3-general_question_template.yml
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
name: 'General Question'
|
||||||
|
description: Create a new ticket for a general question
|
||||||
|
title: '🐛 [General Question] - <title>'
|
||||||
|
labels: ['question']
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: summary
|
||||||
|
attributes:
|
||||||
|
label: 'Summary'
|
||||||
|
description: General Question(s) (for Bugs and Feature Requests, please use the appropriate template)
|
||||||
|
placeholder: '...'
|
||||||
|
validations:
|
||||||
|
required: true
|
0
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
0
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: '18.19.0'
|
||||||
|
|
||||||
- name: Set yarn version
|
- name: Set yarn version
|
||||||
run: yarn set version 1.22.18
|
run: yarn set version 1.22.18
|
||||||
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: '18.19.0'
|
||||||
|
|
||||||
- name: Set yarn version
|
- name: Set yarn version
|
||||||
run: yarn set version 1.22.18
|
run: yarn set version 1.22.18
|
||||||
|
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
|||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: '18.19.0'
|
||||||
|
|
||||||
- name: Checkout Books
|
- name: Checkout Books
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@ -59,7 +59,7 @@ jobs:
|
|||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: '18.19.0'
|
||||||
|
|
||||||
- name: Checkout Books
|
- name: Checkout Books
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
@ -107,7 +107,7 @@ jobs:
|
|||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: '18.19.0'
|
||||||
|
|
||||||
- name: Checkout Books
|
- name: Checkout Books
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
|||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: '18.19.0'
|
||||||
|
|
||||||
- name: Set yarn version
|
- name: Set yarn version
|
||||||
run: yarn set version 1.22.18
|
run: yarn set version 1.22.18
|
||||||
@ -37,7 +37,7 @@ jobs:
|
|||||||
- name: Setup node
|
- name: Setup node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: '16.14.0'
|
node-version: '18.19.0'
|
||||||
|
|
||||||
- name: Set yarn version
|
- name: Set yarn version
|
||||||
run: yarn set version 1.22.18
|
run: yarn set version 1.22.18
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,6 +23,7 @@ yarn-error.log*
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
#Electron-builder output
|
#Electron-builder output
|
||||||
/dist_electron
|
/dist_electron
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
**/types.ts
|
**/types.ts
|
||||||
**/dist_electron
|
**/dist_electron
|
||||||
**/dummy/*.json
|
**/dummy/*.json
|
||||||
|
**/.github/ISSUE_TEMPLATE/*.yml
|
||||||
|
**/patches/v0_21_0/*
|
11
README.md
11
README.md
@ -1,10 +1,3 @@
|
|||||||
> [!IMPORTANT]
|
|
||||||
>
|
|
||||||
> Frappe in search of a maintainer for Frappe Books. For more details check
|
|
||||||
> this issue: [#775](https://github.com/frappe/books/issues/775)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center" markdown="1">
|
<div align="center" markdown="1">
|
||||||
|
|
||||||
<img src="https://user-images.githubusercontent.com/29507195/207267672-d422db6c-d89a-4bbe-9822-468a55c15053.png" alt="Frappe Books logo" width="384"/>
|
<img src="https://user-images.githubusercontent.com/29507195/207267672-d422db6c-d89a-4bbe-9822-468a55c15053.png" alt="Frappe Books logo" width="384"/>
|
||||||
@ -147,6 +140,10 @@ If you want to contribute code then you can fork this repo, make changes and rai
|
|||||||
- [GitHub Discussions](https://github.com/frappe/books/discussions): Used for discussions around a specific topic.
|
- [GitHub Discussions](https://github.com/frappe/books/discussions): Used for discussions around a specific topic.
|
||||||
- [Frappe Books Blog](https://tech.frappebooks.com/): Sporadically updated dev blog regarding the development of this project.
|
- [Frappe Books Blog](https://tech.frappebooks.com/): Sporadically updated dev blog regarding the development of this project.
|
||||||
|
|
||||||
|
## Maintainers
|
||||||
|
|
||||||
|
Frappe Books is currently being maintained by [Mildred Ki'Lya](https://github.com/mildred) and [Isaac-GC](https://github.com/Isaac-GC).
|
||||||
|
|
||||||
## Translation Contributors
|
## Translation Contributors
|
||||||
|
|
||||||
| Language | Contributors |
|
| Language | Contributors |
|
||||||
|
@ -8,8 +8,10 @@ import {
|
|||||||
import { ModelNameEnum } from '../../models/types';
|
import { ModelNameEnum } from '../../models/types';
|
||||||
import DatabaseCore from './core';
|
import DatabaseCore from './core';
|
||||||
import { BespokeFunction } from './types';
|
import { BespokeFunction } from './types';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
import { DocItem, ReturnDocItem } from 'models/inventory/types';
|
import { DocItem, ReturnDocItem } from 'models/inventory/types';
|
||||||
import { safeParseFloat } from 'utils/index';
|
import { safeParseFloat } from 'utils/index';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
|
||||||
export class BespokeQueries {
|
export class BespokeQueries {
|
||||||
[key: string]: BespokeFunction;
|
[key: string]: BespokeFunction;
|
||||||
@ -390,4 +392,59 @@ export class BespokeQueries {
|
|||||||
}
|
}
|
||||||
return returnBalanceItems;
|
return returnBalanceItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getPOSTransactedAmount(
|
||||||
|
db: DatabaseCore,
|
||||||
|
fromDate: Date,
|
||||||
|
toDate: Date,
|
||||||
|
lastShiftClosingDate?: Date
|
||||||
|
): Promise<Record<string, Money> | undefined> {
|
||||||
|
const sinvNamesQuery = db.knex!(ModelNameEnum.SalesInvoice)
|
||||||
|
.select('name')
|
||||||
|
.where('isPOS', true)
|
||||||
|
.andWhereBetween('date', [
|
||||||
|
DateTime.fromJSDate(fromDate).toSQLDate(),
|
||||||
|
DateTime.fromJSDate(toDate).toSQLDate(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (lastShiftClosingDate) {
|
||||||
|
sinvNamesQuery.andWhere(
|
||||||
|
'created',
|
||||||
|
'>',
|
||||||
|
DateTime.fromJSDate(lastShiftClosingDate).toUTC().toString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sinvNames = (await sinvNamesQuery).map(
|
||||||
|
(row: { name: string }) => row.name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!sinvNames.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentEntryNames: string[] = (
|
||||||
|
await db.knex!(ModelNameEnum.PaymentFor)
|
||||||
|
.select('parent')
|
||||||
|
.whereIn('referenceName', sinvNames)
|
||||||
|
).map((doc: { parent: string }) => doc.parent);
|
||||||
|
|
||||||
|
const groupedAmounts = (await db.knex!(ModelNameEnum.Payment)
|
||||||
|
.select('paymentMethod')
|
||||||
|
.whereIn('name', paymentEntryNames)
|
||||||
|
.groupBy('paymentMethod')
|
||||||
|
.sum({ amount: 'amount' })) as { paymentMethod: string; amount: Money }[];
|
||||||
|
|
||||||
|
const transactedAmounts = {} as { [paymentMethod: string]: Money };
|
||||||
|
|
||||||
|
if (!groupedAmounts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const row of groupedAmounts) {
|
||||||
|
transactedAmounts[row.paymentMethod] = row.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactedAmounts;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import createInventoryNumberSeries from './createInventoryNumberSeries';
|
|||||||
import fixRoundOffAccount from './fixRoundOffAccount';
|
import fixRoundOffAccount from './fixRoundOffAccount';
|
||||||
import testPatch from './testPatch';
|
import testPatch from './testPatch';
|
||||||
import updateSchemas from './updateSchemas';
|
import updateSchemas from './updateSchemas';
|
||||||
|
import setPaymentReferenceType from './setPaymentReferenceType';
|
||||||
|
import fixLedgerDateTime from './v0_21_0/fixLedgerDateTime';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{ name: 'testPatch', version: '0.5.0-beta.0', patch: testPatch },
|
{ name: 'testPatch', version: '0.5.0-beta.0', patch: testPatch },
|
||||||
@ -28,4 +30,14 @@ export default [
|
|||||||
version: '0.6.6-beta.0',
|
version: '0.6.6-beta.0',
|
||||||
patch: createInventoryNumberSeries,
|
patch: createInventoryNumberSeries,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'setPaymentReferenceType',
|
||||||
|
version: '0.20.1',
|
||||||
|
patch: setPaymentReferenceType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fixLedgerDateTime',
|
||||||
|
version: '0.21.2',
|
||||||
|
patch: fixLedgerDateTime,
|
||||||
|
},
|
||||||
] as Patch[];
|
] as Patch[];
|
||||||
|
12
backend/patches/setPaymentReferenceType.ts
Normal file
12
backend/patches/setPaymentReferenceType.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { DatabaseManager } from '../database/manager';
|
||||||
|
|
||||||
|
async function execute(dm: DatabaseManager) {
|
||||||
|
await dm.db!.knex!('Payment')
|
||||||
|
.where({ referenceType: null, paymentType: 'Pay' })
|
||||||
|
.update({ referenceType: 'PurchaseInvoice' });
|
||||||
|
await dm.db!.knex!('Payment')
|
||||||
|
.where({ referenceType: null, paymentType: 'Receive' })
|
||||||
|
.update({ referenceType: 'SalesInvoice' });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { execute, beforeMigrate: true };
|
@ -21,6 +21,7 @@ const defaultNumberSeriesMap = {
|
|||||||
[ModelNameEnum.JournalEntry]: 'JV-',
|
[ModelNameEnum.JournalEntry]: 'JV-',
|
||||||
[ModelNameEnum.SalesInvoice]: 'SINV-',
|
[ModelNameEnum.SalesInvoice]: 'SINV-',
|
||||||
[ModelNameEnum.PurchaseInvoice]: 'PINV-',
|
[ModelNameEnum.PurchaseInvoice]: 'PINV-',
|
||||||
|
[ModelNameEnum.SalesQuote]: 'SQUOT-',
|
||||||
} as Record<ModelNameEnum, string>;
|
} as Record<ModelNameEnum, string>;
|
||||||
|
|
||||||
async function execute(dm: DatabaseManager) {
|
async function execute(dm: DatabaseManager) {
|
||||||
@ -209,6 +210,7 @@ async function copyTransactionalTables(
|
|||||||
ModelNameEnum.Payment,
|
ModelNameEnum.Payment,
|
||||||
ModelNameEnum.SalesInvoice,
|
ModelNameEnum.SalesInvoice,
|
||||||
ModelNameEnum.PurchaseInvoice,
|
ModelNameEnum.PurchaseInvoice,
|
||||||
|
ModelNameEnum.SalesQuote,
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const sn of schemaNames) {
|
for (const sn of schemaNames) {
|
||||||
|
40
backend/patches/v0_21_0/fixLedgerDateTime.ts
Normal file
40
backend/patches/v0_21_0/fixLedgerDateTime.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { DatabaseManager } from '../../database/manager';
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
async function execute(dm: DatabaseManager) {
|
||||||
|
|
||||||
|
const sourceTables = [
|
||||||
|
"PurchaseInvoice",
|
||||||
|
"SalesInvoice",
|
||||||
|
"JournalEntry",
|
||||||
|
"Payment",
|
||||||
|
"StockMovement",
|
||||||
|
"StockTransfer"
|
||||||
|
];
|
||||||
|
|
||||||
|
await dm.db!.knex!('AccountingLedgerEntry')
|
||||||
|
.select('name', 'date', 'referenceName')
|
||||||
|
.then((trx: Array<{name: string; date: Date; referenceName: string;}> ) => {
|
||||||
|
trx.forEach(async entry => {
|
||||||
|
|
||||||
|
sourceTables.forEach(async table => {
|
||||||
|
await dm.db!.knex!
|
||||||
|
.select('name','date')
|
||||||
|
.from(table)
|
||||||
|
.where({ name: entry['referenceName'] })
|
||||||
|
.then(async (resp: Array<{name: string; date: Date;}>) => {
|
||||||
|
if (resp.length !== 0) {
|
||||||
|
|
||||||
|
const dateTimeValue = new Date(resp[0]['date']);
|
||||||
|
await dm.db!.knex!('AccountingLedgerEntry')
|
||||||
|
.where({ name: entry['name'] })
|
||||||
|
.update({ date: dateTimeValue.toISOString() });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { execute, beforeMigrate: true };
|
||||||
|
/* eslint-enable */
|
@ -8,6 +8,7 @@ import * as vite from 'vite';
|
|||||||
import { getMainProcessCommonConfig } from './helpers.mjs';
|
import { getMainProcessCommonConfig } from './helpers.mjs';
|
||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
import { hideBin } from 'yargs/helpers';
|
import { hideBin } from 'yargs/helpers';
|
||||||
|
import frappeBooksConfig from '../../electron-builder-config.mjs';
|
||||||
|
|
||||||
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const root = path.join(dirname, '..', '..');
|
const root = path.join(dirname, '..', '..');
|
||||||
@ -153,12 +154,8 @@ async function packageApp() {
|
|||||||
delete builderArgs[opt];
|
delete builderArgs[opt];
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildOptions = {
|
let buildOptions = {
|
||||||
config: {
|
config: frappeBooksConfig,
|
||||||
directories: { output: packageDirPath, app: buildDirPath },
|
|
||||||
files: ['**'],
|
|
||||||
extends: null,
|
|
||||||
},
|
|
||||||
...builderArgs,
|
...builderArgs,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
69
electron-builder-config.mjs
Normal file
69
electron-builder-config.mjs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
// App is tagged with a .mjs extension to allow
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* electron-builder doesn't look for the APPLE_TEAM_ID environment variable for some reason.
|
||||||
|
* This workaround allows an environment variable to be added to the electron-builder.yml config
|
||||||
|
* collection. See: https://github.com/electron-userland/electron-builder/issues/7812
|
||||||
|
*/
|
||||||
|
|
||||||
|
const dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
// const root = path.join(dirname, '..', '..');
|
||||||
|
const root = dirname; // redundant, but is meant to keep with the previous line
|
||||||
|
const buildDirPath = path.join(root, 'dist_electron', 'build');
|
||||||
|
const packageDirPath = path.join(root, 'dist_electron', 'bundled');
|
||||||
|
|
||||||
|
const frappeBooksConfig = {
|
||||||
|
productName: 'Frappe Books',
|
||||||
|
appId: 'io.frappe.books',
|
||||||
|
asarUnpack: '**/*.node',
|
||||||
|
extraResources: [
|
||||||
|
{ from: 'log_creds.txt', to: '../creds/log_creds.txt' },
|
||||||
|
{ from: 'translations', to: '../translations' },
|
||||||
|
{ from: 'templates', to: '../templates' },
|
||||||
|
],
|
||||||
|
files: '**',
|
||||||
|
extends: null,
|
||||||
|
directories: {
|
||||||
|
output: packageDirPath,
|
||||||
|
app: buildDirPath,
|
||||||
|
},
|
||||||
|
mac: {
|
||||||
|
type: 'distribution',
|
||||||
|
category: 'public.app-category.finance',
|
||||||
|
icon: 'build/icon.icns',
|
||||||
|
notarize: {
|
||||||
|
teamId: process.env.APPLE_TEAM_ID || '',
|
||||||
|
},
|
||||||
|
hardenedRuntime: true,
|
||||||
|
gatekeeperAssess: false,
|
||||||
|
darkModeSupport: false,
|
||||||
|
entitlements: 'build/entitlements.mac.plist',
|
||||||
|
entitlementsInherit: 'build/entitlements.mac.plist',
|
||||||
|
publish: ['github'],
|
||||||
|
},
|
||||||
|
win: {
|
||||||
|
publisherName: 'Frappe Technologies Pvt. Ltd.',
|
||||||
|
signDlls: true,
|
||||||
|
icon: 'build/icon.ico',
|
||||||
|
publish: ['github'],
|
||||||
|
target: ['nsis', 'portable'],
|
||||||
|
},
|
||||||
|
nsis: {
|
||||||
|
oneClick: false,
|
||||||
|
perMachine: false,
|
||||||
|
allowToChangeInstallationDirectory: true,
|
||||||
|
installerIcon: 'build/installericon.ico',
|
||||||
|
uninstallerIcon: 'build/uninstallericon.ico',
|
||||||
|
publish: ['github'],
|
||||||
|
},
|
||||||
|
linux: {
|
||||||
|
icon: 'build/icons',
|
||||||
|
category: 'Finance',
|
||||||
|
publish: ['github'],
|
||||||
|
target: ['deb', 'AppImage', 'rpm'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default frappeBooksConfig;
|
@ -11,8 +11,8 @@ mac:
|
|||||||
type: distribution
|
type: distribution
|
||||||
category: public.app-category.finance
|
category: public.app-category.finance
|
||||||
icon: build/icon.icns
|
icon: build/icon.icns
|
||||||
notarize:
|
# notarize:
|
||||||
appBundleId: io.frappe.books
|
# appBundleId: io.frappe.books
|
||||||
hardenedRuntime: true
|
hardenedRuntime: true
|
||||||
gatekeeperAssess: false
|
gatekeeperAssess: false
|
||||||
darkModeSupport: false
|
darkModeSupport: false
|
@ -27,6 +27,7 @@ import {
|
|||||||
RawValueMap,
|
RawValueMap,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { ReturnDocItem } from 'models/inventory/types';
|
import { ReturnDocItem } from 'models/inventory/types';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
|
||||||
type FieldMap = Record<string, Record<string, Field>>;
|
type FieldMap = Record<string, Record<string, Field>>;
|
||||||
|
|
||||||
@ -342,6 +343,19 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
)) as Promise<Record<string, ReturnDocItem> | undefined>;
|
)) as Promise<Record<string, ReturnDocItem> | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getPOSTransactedAmount(
|
||||||
|
fromDate: Date,
|
||||||
|
toDate: Date,
|
||||||
|
lastShiftClosingDate?: Date
|
||||||
|
): Promise<Record<string, Money> | undefined> {
|
||||||
|
return (await this.#demux.callBespoke(
|
||||||
|
'getPOSTransactedAmount',
|
||||||
|
fromDate,
|
||||||
|
toDate,
|
||||||
|
lastShiftClosingDate
|
||||||
|
)) as Promise<Record<string, Money> | undefined>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal methods
|
* Internal methods
|
||||||
*/
|
*/
|
||||||
|
@ -10,6 +10,8 @@ import type { Defaults } from 'models/baseModels/Defaults/Defaults';
|
|||||||
import type { PrintSettings } from 'models/baseModels/PrintSettings/PrintSettings';
|
import type { PrintSettings } from 'models/baseModels/PrintSettings/PrintSettings';
|
||||||
import type { InventorySettings } from 'models/inventory/InventorySettings';
|
import type { InventorySettings } from 'models/inventory/InventorySettings';
|
||||||
import type { Misc } from 'models/baseModels/Misc';
|
import type { Misc } from 'models/baseModels/Misc';
|
||||||
|
import type { POSSettings } from 'models/inventory/Point of Sale/POSSettings';
|
||||||
|
import type { POSShift } from 'models/inventory/Point of Sale/POSShift';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The functions below are used for dynamic evaluation
|
* The functions below are used for dynamic evaluation
|
||||||
@ -54,6 +56,8 @@ export interface SinglesMap {
|
|||||||
SystemSettings?: SystemSettings;
|
SystemSettings?: SystemSettings;
|
||||||
AccountingSettings?: AccountingSettings;
|
AccountingSettings?: AccountingSettings;
|
||||||
InventorySettings?: InventorySettings;
|
InventorySettings?: InventorySettings;
|
||||||
|
POSSettings?: POSSettings;
|
||||||
|
POSShift?: POSShift;
|
||||||
PrintSettings?: PrintSettings;
|
PrintSettings?: PrintSettings;
|
||||||
Defaults?: Defaults;
|
Defaults?: Defaults;
|
||||||
Misc?: Misc;
|
Misc?: Misc;
|
||||||
|
@ -20,14 +20,12 @@ export async function saveHtmlAsPdf(
|
|||||||
|
|
||||||
const printWindow = await getInitializedPrintWindow(htmlPath, width, height);
|
const printWindow = await getInitializedPrintWindow(htmlPath, width, height);
|
||||||
const printOptions = {
|
const printOptions = {
|
||||||
marginsType: 1, // no margin
|
margins: { top: 0, bottom: 0, left: 0, right: 0 }, // equivalent to previous 'marginType: 1'
|
||||||
pageSize: {
|
pageSize: {
|
||||||
height: height * 10_000, // micrometers
|
height: height / 2.54, // Convert from centimeters to inches
|
||||||
width: width * 10_000, // micrometers
|
width: width / 2.54, // Convert from centimeters to inches
|
||||||
},
|
},
|
||||||
printBackground: true,
|
printBackground: true,
|
||||||
printBackgrounds: true,
|
|
||||||
printSelectionOnly: false,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = await printWindow.webContents.printToPDF(printOptions);
|
const data = await printWindow.webContents.printToPDF(printOptions);
|
||||||
|
@ -61,6 +61,21 @@ export class LedgerPosting {
|
|||||||
this._validateIsEqual();
|
this._validateIsEqual();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timezoneDateTimeAdjuster(setDate: string | Date) {
|
||||||
|
const dateTimeValue = new Date(setDate);
|
||||||
|
|
||||||
|
const dtFixedValue = dateTimeValue;
|
||||||
|
const dtMinutes = dtFixedValue.getTimezoneOffset() % 60;
|
||||||
|
const dtHours = (dtFixedValue.getTimezoneOffset() - dtMinutes) / 60;
|
||||||
|
// Forcing the time to always be set to 00:00.000 for locale time
|
||||||
|
dtFixedValue.setHours(0 - dtHours);
|
||||||
|
dtFixedValue.setMinutes(0 - dtMinutes);
|
||||||
|
dtFixedValue.setSeconds(0);
|
||||||
|
dtFixedValue.setMilliseconds(0);
|
||||||
|
|
||||||
|
return dtFixedValue;
|
||||||
|
}
|
||||||
|
|
||||||
async makeRoundOffEntry() {
|
async makeRoundOffEntry() {
|
||||||
const { debit, credit } = this._getTotalDebitAndCredit();
|
const { debit, credit } = this._getTotalDebitAndCredit();
|
||||||
const difference = debit.sub(credit);
|
const difference = debit.sub(credit);
|
||||||
@ -90,12 +105,14 @@ export class LedgerPosting {
|
|||||||
return map[account];
|
return map[account];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// end ugly timezone fix code
|
||||||
|
|
||||||
const ledgerEntry = this.fyo.doc.getNewDoc(
|
const ledgerEntry = this.fyo.doc.getNewDoc(
|
||||||
ModelNameEnum.AccountingLedgerEntry,
|
ModelNameEnum.AccountingLedgerEntry,
|
||||||
{
|
{
|
||||||
account: account,
|
account: account,
|
||||||
party: (this.refDoc.party as string) ?? '',
|
party: (this.refDoc.party as string) ?? '',
|
||||||
date: this.refDoc.date as string | Date,
|
date: this.timezoneDateTimeAdjuster(this.refDoc.date as string | Date),
|
||||||
referenceType: this.refDoc.schemaName,
|
referenceType: this.refDoc.schemaName,
|
||||||
referenceName: this.refDoc.name!,
|
referenceName: this.refDoc.name!,
|
||||||
reverted: this.reverted,
|
reverted: this.reverted,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import { DefaultCashDenominations } from 'models/inventory/Point of Sale/DefaultCashDenominations';
|
||||||
import { Doc } from 'fyo/model/doc';
|
import { Doc } from 'fyo/model/doc';
|
||||||
import { FiltersMap, HiddenMap } from 'fyo/model/types';
|
import { FiltersMap, HiddenMap } from 'fyo/model/types';
|
||||||
import { ModelNameEnum } from 'models/types';
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { PartyRoleEnum } from '../Party/types';
|
||||||
|
|
||||||
export class Defaults extends Doc {
|
export class Defaults extends Doc {
|
||||||
// Auto Payments
|
// Auto Payments
|
||||||
@ -12,6 +14,7 @@ export class Defaults extends Doc {
|
|||||||
purchaseReceiptLocation?: string;
|
purchaseReceiptLocation?: string;
|
||||||
|
|
||||||
// Number Series
|
// Number Series
|
||||||
|
salesQuoteNumberSeries?: string;
|
||||||
salesInvoiceNumberSeries?: string;
|
salesInvoiceNumberSeries?: string;
|
||||||
purchaseInvoiceNumberSeries?: string;
|
purchaseInvoiceNumberSeries?: string;
|
||||||
journalEntryNumberSeries?: string;
|
journalEntryNumberSeries?: string;
|
||||||
@ -27,6 +30,7 @@ export class Defaults extends Doc {
|
|||||||
purchaseReceiptTerms?: string;
|
purchaseReceiptTerms?: string;
|
||||||
|
|
||||||
// Print Templates
|
// Print Templates
|
||||||
|
salesQuotePrintTemplate?: string;
|
||||||
salesInvoicePrintTemplate?: string;
|
salesInvoicePrintTemplate?: string;
|
||||||
purchaseInvoicePrintTemplate?: string;
|
purchaseInvoicePrintTemplate?: string;
|
||||||
journalEntryPrintTemplate?: string;
|
journalEntryPrintTemplate?: string;
|
||||||
@ -35,11 +39,18 @@ export class Defaults extends Doc {
|
|||||||
purchaseReceiptPrintTemplate?: string;
|
purchaseReceiptPrintTemplate?: string;
|
||||||
stockMovementPrintTemplate?: string;
|
stockMovementPrintTemplate?: string;
|
||||||
|
|
||||||
|
// Point of Sale
|
||||||
|
posCashDenominations?: DefaultCashDenominations[];
|
||||||
|
posCustomer?: string;
|
||||||
|
|
||||||
static commonFilters = {
|
static commonFilters = {
|
||||||
// Auto Payments
|
// Auto Payments
|
||||||
salesPaymentAccount: () => ({ isGroup: false, accountType: 'Cash' }),
|
salesPaymentAccount: () => ({ isGroup: false, accountType: 'Cash' }),
|
||||||
purchasePaymentAccount: () => ({ isGroup: false, accountType: 'Cash' }),
|
purchasePaymentAccount: () => ({ isGroup: false, accountType: 'Cash' }),
|
||||||
// Number Series
|
// Number Series
|
||||||
|
salesQuoteNumberSeries: () => ({
|
||||||
|
referenceType: ModelNameEnum.SalesQuote,
|
||||||
|
}),
|
||||||
salesInvoiceNumberSeries: () => ({
|
salesInvoiceNumberSeries: () => ({
|
||||||
referenceType: ModelNameEnum.SalesInvoice,
|
referenceType: ModelNameEnum.SalesInvoice,
|
||||||
}),
|
}),
|
||||||
@ -62,6 +73,7 @@ export class Defaults extends Doc {
|
|||||||
referenceType: ModelNameEnum.PurchaseReceipt,
|
referenceType: ModelNameEnum.PurchaseReceipt,
|
||||||
}),
|
}),
|
||||||
// Print Templates
|
// Print Templates
|
||||||
|
salesQuotePrintTemplate: () => ({ type: ModelNameEnum.SalesQuote }),
|
||||||
salesInvoicePrintTemplate: () => ({ type: ModelNameEnum.SalesInvoice }),
|
salesInvoicePrintTemplate: () => ({ type: ModelNameEnum.SalesInvoice }),
|
||||||
purchaseInvoicePrintTemplate: () => ({
|
purchaseInvoicePrintTemplate: () => ({
|
||||||
type: ModelNameEnum.PurchaseInvoice,
|
type: ModelNameEnum.PurchaseInvoice,
|
||||||
@ -73,6 +85,7 @@ export class Defaults extends Doc {
|
|||||||
type: ModelNameEnum.PurchaseReceipt,
|
type: ModelNameEnum.PurchaseReceipt,
|
||||||
}),
|
}),
|
||||||
stockMovementPrintTemplate: () => ({ type: ModelNameEnum.StockMovement }),
|
stockMovementPrintTemplate: () => ({ type: ModelNameEnum.StockMovement }),
|
||||||
|
posCustomer: () => ({ role: PartyRoleEnum.Customer }),
|
||||||
};
|
};
|
||||||
|
|
||||||
static filters: FiltersMap = this.commonFilters;
|
static filters: FiltersMap = this.commonFilters;
|
||||||
@ -82,6 +95,10 @@ export class Defaults extends Doc {
|
|||||||
return () => !this.fyo.singles.AccountingSettings?.enableInventory;
|
return () => !this.fyo.singles.AccountingSettings?.enableInventory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getPointOfSaleHidden() {
|
||||||
|
return () => !this.fyo.singles.InventorySettings?.enablePointOfSale;
|
||||||
|
}
|
||||||
|
|
||||||
hidden: HiddenMap = {
|
hidden: HiddenMap = {
|
||||||
stockMovementNumberSeries: this.getInventoryHidden(),
|
stockMovementNumberSeries: this.getInventoryHidden(),
|
||||||
shipmentNumberSeries: this.getInventoryHidden(),
|
shipmentNumberSeries: this.getInventoryHidden(),
|
||||||
@ -91,6 +108,8 @@ export class Defaults extends Doc {
|
|||||||
shipmentPrintTemplate: this.getInventoryHidden(),
|
shipmentPrintTemplate: this.getInventoryHidden(),
|
||||||
purchaseReceiptPrintTemplate: this.getInventoryHidden(),
|
purchaseReceiptPrintTemplate: this.getInventoryHidden(),
|
||||||
stockMovementPrintTemplate: this.getInventoryHidden(),
|
stockMovementPrintTemplate: this.getInventoryHidden(),
|
||||||
|
posCashDenominations: this.getPointOfSaleHidden(),
|
||||||
|
posCustomer: this.getPointOfSaleHidden(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,4 +124,5 @@ export const numberSeriesDefaultsMap: Record<
|
|||||||
[ModelNameEnum.StockMovement]: 'stockMovementNumberSeries',
|
[ModelNameEnum.StockMovement]: 'stockMovementNumberSeries',
|
||||||
[ModelNameEnum.Shipment]: 'shipmentNumberSeries',
|
[ModelNameEnum.Shipment]: 'shipmentNumberSeries',
|
||||||
[ModelNameEnum.PurchaseReceipt]: 'purchaseReceiptNumberSeries',
|
[ModelNameEnum.PurchaseReceipt]: 'purchaseReceiptNumberSeries',
|
||||||
|
[ModelNameEnum.SalesQuote]: 'salesQuoteNumberSeries',
|
||||||
};
|
};
|
||||||
|
@ -28,6 +28,19 @@ import { TaxSummary } from '../TaxSummary/TaxSummary';
|
|||||||
import { ReturnDocItem } from 'models/inventory/types';
|
import { ReturnDocItem } from 'models/inventory/types';
|
||||||
import { AccountFieldEnum, PaymentTypeEnum } from '../Payment/types';
|
import { AccountFieldEnum, PaymentTypeEnum } from '../Payment/types';
|
||||||
|
|
||||||
|
export type TaxDetail = {
|
||||||
|
account: string;
|
||||||
|
payment_account?: string;
|
||||||
|
rate: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InvoiceTaxItem = {
|
||||||
|
details: TaxDetail;
|
||||||
|
exchangeRate?: number;
|
||||||
|
fullAmount: Money;
|
||||||
|
taxAmount: Money;
|
||||||
|
};
|
||||||
|
|
||||||
export abstract class Invoice extends Transactional {
|
export abstract class Invoice extends Transactional {
|
||||||
_taxes: Record<string, Tax> = {};
|
_taxes: Record<string, Tax> = {};
|
||||||
taxes?: TaxSummary[];
|
taxes?: TaxSummary[];
|
||||||
@ -58,7 +71,13 @@ export abstract class Invoice extends Transactional {
|
|||||||
returnAgainst?: string;
|
returnAgainst?: string;
|
||||||
|
|
||||||
get isSales() {
|
get isSales() {
|
||||||
return this.schemaName === 'SalesInvoice';
|
return (
|
||||||
|
this.schemaName === 'SalesInvoice' || this.schemaName == 'SalesQuote'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isQuote() {
|
||||||
|
return this.schemaName == 'SalesQuote';
|
||||||
}
|
}
|
||||||
|
|
||||||
get enableDiscounting() {
|
get enableDiscounting() {
|
||||||
@ -242,6 +261,38 @@ export abstract class Invoice extends Transactional {
|
|||||||
return safeParseFloat(exchangeRate.toFixed(2));
|
return safeParseFloat(exchangeRate.toFixed(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTaxItems(): Promise<InvoiceTaxItem[]> {
|
||||||
|
const taxItems: InvoiceTaxItem[] = [];
|
||||||
|
for (const item of this.items ?? []) {
|
||||||
|
if (!item.tax) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tax = await this.getTax(item.tax);
|
||||||
|
for (const details of (tax.details ?? []) as TaxDetail[]) {
|
||||||
|
let amount = item.amount!;
|
||||||
|
if (
|
||||||
|
this.enableDiscounting &&
|
||||||
|
!this.discountAfterTax &&
|
||||||
|
!item.itemDiscountedTotal?.isZero()
|
||||||
|
) {
|
||||||
|
amount = item.itemDiscountedTotal!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taxItem: InvoiceTaxItem = {
|
||||||
|
details,
|
||||||
|
exchangeRate: this.exchangeRate ?? 1,
|
||||||
|
fullAmount: amount,
|
||||||
|
taxAmount: amount.mul(details.rate / 100),
|
||||||
|
};
|
||||||
|
|
||||||
|
taxItems.push(taxItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return taxItems;
|
||||||
|
}
|
||||||
|
|
||||||
async getTaxSummary() {
|
async getTaxSummary() {
|
||||||
const taxes: Record<
|
const taxes: Record<
|
||||||
string,
|
string,
|
||||||
@ -252,34 +303,17 @@ export abstract class Invoice extends Transactional {
|
|||||||
}
|
}
|
||||||
> = {};
|
> = {};
|
||||||
|
|
||||||
type TaxDetail = { account: string; rate: number };
|
for (const { details, taxAmount } of await this.getTaxItems()) {
|
||||||
|
const account = details.account;
|
||||||
|
|
||||||
for (const item of this.items ?? []) {
|
|
||||||
if (!item.tax) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tax = await this.getTax(item.tax);
|
|
||||||
for (const { account, rate } of (tax.details ?? []) as TaxDetail[]) {
|
|
||||||
taxes[account] ??= {
|
taxes[account] ??= {
|
||||||
account,
|
account,
|
||||||
rate,
|
rate: details.rate,
|
||||||
amount: this.fyo.pesa(0),
|
amount: this.fyo.pesa(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
let amount = item.amount!;
|
|
||||||
if (
|
|
||||||
this.enableDiscounting &&
|
|
||||||
!this.discountAfterTax &&
|
|
||||||
!item.itemDiscountedTotal?.isZero()
|
|
||||||
) {
|
|
||||||
amount = item.itemDiscountedTotal!;
|
|
||||||
}
|
|
||||||
|
|
||||||
const taxAmount = amount.mul(rate / 100);
|
|
||||||
taxes[account].amount = taxes[account].amount.add(taxAmount);
|
taxes[account].amount = taxes[account].amount.add(taxAmount);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
type Summary = typeof taxes[string] & { idx: number };
|
type Summary = typeof taxes[string] & { idx: number };
|
||||||
const taxArr: Summary[] = [];
|
const taxArr: Summary[] = [];
|
||||||
@ -465,7 +499,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _updateIsItemsReturned() {
|
async _updateIsItemsReturned() {
|
||||||
if (!this.isReturn || !this.returnAgainst) {
|
if (!this.isReturn || !this.returnAgainst || this.isQuote) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -487,7 +521,7 @@ export abstract class Invoice extends Transactional {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _validateHasLinkedReturnInvoices() {
|
async _validateHasLinkedReturnInvoices() {
|
||||||
if (!this.name || this.isReturn) {
|
if (!this.name || this.isReturn || this.isQuote) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -657,7 +691,10 @@ export abstract class Invoice extends Transactional {
|
|||||||
attachment: () =>
|
attachment: () =>
|
||||||
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
||||||
backReference: () => !this.backReference,
|
backReference: () => !this.backReference,
|
||||||
priceList: () => !this.fyo.singles.AccountingSettings?.enablePriceList,
|
quote: () => !this.quote,
|
||||||
|
priceList: () =>
|
||||||
|
!this.fyo.singles.AccountingSettings?.enablePriceList ||
|
||||||
|
(!this.canEdit && !this.priceList),
|
||||||
returnAgainst: () =>
|
returnAgainst: () =>
|
||||||
(this.isSubmitted || this.isCancelled) && !this.returnAgainst,
|
(this.isSubmitted || this.isCancelled) && !this.returnAgainst,
|
||||||
};
|
};
|
||||||
|
@ -47,7 +47,10 @@ export abstract class InvoiceItem extends Doc {
|
|||||||
itemTaxedTotal?: Money;
|
itemTaxedTotal?: Money;
|
||||||
|
|
||||||
get isSales() {
|
get isSales() {
|
||||||
return this.schemaName === 'SalesInvoiceItem';
|
return (
|
||||||
|
this.schemaName === 'SalesInvoiceItem' ||
|
||||||
|
this.schemaName === 'SalesQuoteItem'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get date() {
|
get date() {
|
||||||
|
@ -28,10 +28,12 @@ import { Invoice } from '../Invoice/Invoice';
|
|||||||
import { Party } from '../Party/Party';
|
import { Party } from '../Party/Party';
|
||||||
import { PaymentFor } from '../PaymentFor/PaymentFor';
|
import { PaymentFor } from '../PaymentFor/PaymentFor';
|
||||||
import { PaymentMethod, PaymentType } from './types';
|
import { PaymentMethod, PaymentType } from './types';
|
||||||
|
import { TaxSummary } from '../TaxSummary/TaxSummary';
|
||||||
|
|
||||||
type AccountTypeMap = Record<AccountTypeEnum, string[] | undefined>;
|
type AccountTypeMap = Record<AccountTypeEnum, string[] | undefined>;
|
||||||
|
|
||||||
export class Payment extends Transactional {
|
export class Payment extends Transactional {
|
||||||
|
taxes?: TaxSummary[];
|
||||||
party?: string;
|
party?: string;
|
||||||
amount?: Money;
|
amount?: Money;
|
||||||
writeoff?: Money;
|
writeoff?: Money;
|
||||||
@ -221,6 +223,86 @@ export class Payment extends Transactional {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTaxSummary() {
|
||||||
|
const taxes: Record<
|
||||||
|
string,
|
||||||
|
Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
account: string;
|
||||||
|
from_account: string;
|
||||||
|
rate: number;
|
||||||
|
amount: Money;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
> = {};
|
||||||
|
|
||||||
|
for (const childDoc of this.for ?? []) {
|
||||||
|
const referenceName = childDoc.referenceName;
|
||||||
|
const referenceType = childDoc.referenceType;
|
||||||
|
|
||||||
|
const refDoc = (await this.fyo.doc.getDoc(
|
||||||
|
childDoc.referenceType!,
|
||||||
|
childDoc.referenceName
|
||||||
|
)) as Invoice;
|
||||||
|
|
||||||
|
if (referenceName && referenceType && !refDoc) {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`${referenceType} of type ${
|
||||||
|
this.fyo.schemaMap?.[referenceType]?.label ?? referenceType
|
||||||
|
} does not exist`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!refDoc) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const {
|
||||||
|
details,
|
||||||
|
taxAmount,
|
||||||
|
exchangeRate,
|
||||||
|
} of await refDoc.getTaxItems()) {
|
||||||
|
const { account, payment_account } = details;
|
||||||
|
if (!payment_account) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
taxes[payment_account] ??= {};
|
||||||
|
taxes[payment_account][account] ??= {
|
||||||
|
account: payment_account,
|
||||||
|
from_account: account,
|
||||||
|
rate: details.rate,
|
||||||
|
amount: this.fyo.pesa(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
taxes[payment_account][account].amount = taxes[payment_account][
|
||||||
|
account
|
||||||
|
].amount.add(taxAmount.mul(exchangeRate ?? 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Summary = typeof taxes[string][string] & { idx: number };
|
||||||
|
const taxArr: Summary[] = [];
|
||||||
|
let idx = 0;
|
||||||
|
for (const payment_account in taxes) {
|
||||||
|
for (const account in taxes[payment_account]) {
|
||||||
|
const tax = taxes[payment_account][account];
|
||||||
|
if (tax.amount.isZero()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
taxArr.push({
|
||||||
|
...tax,
|
||||||
|
idx,
|
||||||
|
});
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return taxArr;
|
||||||
|
}
|
||||||
|
|
||||||
async getPosting() {
|
async getPosting() {
|
||||||
/**
|
/**
|
||||||
* account : From Account
|
* account : From Account
|
||||||
@ -244,6 +326,20 @@ export class Payment extends Transactional {
|
|||||||
await posting.debit(paymentAccount, amount);
|
await posting.debit(paymentAccount, amount);
|
||||||
await posting.credit(account, amount);
|
await posting.credit(account, amount);
|
||||||
|
|
||||||
|
if (this.taxes) {
|
||||||
|
if (this.paymentType === 'Receive') {
|
||||||
|
for (const tax of this.taxes) {
|
||||||
|
await posting.debit(tax.from_account!, tax.amount!);
|
||||||
|
await posting.credit(tax.account!, tax.amount!);
|
||||||
|
}
|
||||||
|
} else if (this.paymentType === 'Pay') {
|
||||||
|
for (const tax of this.taxes) {
|
||||||
|
await posting.credit(tax.from_account!, tax.amount!);
|
||||||
|
await posting.debit(tax.account!, tax.amount!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await this.applyWriteOffPosting(posting);
|
await this.applyWriteOffPosting(posting);
|
||||||
return posting;
|
return posting;
|
||||||
}
|
}
|
||||||
@ -508,7 +604,7 @@ export class Payment extends Transactional {
|
|||||||
const outstanding = partyDoc.outstandingAmount as Money;
|
const outstanding = partyDoc.outstandingAmount as Money;
|
||||||
|
|
||||||
if (outstanding.isNegative()) {
|
if (outstanding.isNegative()) {
|
||||||
if (this.referenceType === ModelNameEnum.SalesInvoice) {
|
if (this.referenceType === ModelNameEnum.PurchaseInvoice) {
|
||||||
return 'Pay';
|
return 'Pay';
|
||||||
}
|
}
|
||||||
return 'Receive';
|
return 'Receive';
|
||||||
@ -546,6 +642,7 @@ export class Payment extends Transactional {
|
|||||||
return this.for![0].referenceType;
|
return this.for![0].referenceType;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
taxes: { formula: async () => await this.getTaxSummary() },
|
||||||
};
|
};
|
||||||
|
|
||||||
validations: ValidationMap = {
|
validations: ValidationMap = {
|
||||||
@ -588,6 +685,7 @@ export class Payment extends Transactional {
|
|||||||
attachment: () =>
|
attachment: () =>
|
||||||
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
!(this.attachment || !(this.isSubmitted || this.isCancelled)),
|
||||||
for: () => !!((this.isSubmitted || this.isCancelled) && !this.for?.length),
|
for: () => !!((this.isSubmitted || this.isCancelled) && !this.for?.length),
|
||||||
|
taxes: () => !this.taxes?.length,
|
||||||
};
|
};
|
||||||
|
|
||||||
static filters: FiltersMap = {
|
static filters: FiltersMap = {
|
||||||
|
@ -55,6 +55,7 @@ export class PrintTemplate extends Doc {
|
|||||||
|
|
||||||
const models = [
|
const models = [
|
||||||
ModelNameEnum.SalesInvoice,
|
ModelNameEnum.SalesInvoice,
|
||||||
|
ModelNameEnum.SalesQuote,
|
||||||
ModelNameEnum.PurchaseInvoice,
|
ModelNameEnum.PurchaseInvoice,
|
||||||
ModelNameEnum.JournalEntry,
|
ModelNameEnum.JournalEntry,
|
||||||
ModelNameEnum.Payment,
|
ModelNameEnum.Payment,
|
||||||
|
67
models/baseModels/SalesQuote/SalesQuote.ts
Normal file
67
models/baseModels/SalesQuote/SalesQuote.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Fyo } from 'fyo';
|
||||||
|
import { DocValueMap } from 'fyo/core/types';
|
||||||
|
import { Action, ListViewSettings } from 'fyo/model/types';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { getQuoteActions, getTransactionStatusColumn } from '../../helpers';
|
||||||
|
import { Invoice } from '../Invoice/Invoice';
|
||||||
|
import { SalesQuoteItem } from '../SalesQuoteItem/SalesQuoteItem';
|
||||||
|
import { Defaults } from '../Defaults/Defaults';
|
||||||
|
|
||||||
|
export class SalesQuote extends Invoice {
|
||||||
|
items?: SalesQuoteItem[];
|
||||||
|
|
||||||
|
// This is an inherited method and it must keep the async from the parent
|
||||||
|
// class
|
||||||
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
|
async getPosting() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInvoice(): Promise<Invoice | null> {
|
||||||
|
if (!this.isSubmitted) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const schemaName = ModelNameEnum.SalesInvoice;
|
||||||
|
const defaults = (this.fyo.singles.Defaults as Defaults) ?? {};
|
||||||
|
const terms = defaults.salesInvoiceTerms ?? '';
|
||||||
|
const numberSeries = defaults.salesInvoiceNumberSeries ?? undefined;
|
||||||
|
|
||||||
|
const data: DocValueMap = {
|
||||||
|
...this.getValidDict(false, true),
|
||||||
|
date: new Date().toISOString(),
|
||||||
|
terms,
|
||||||
|
numberSeries,
|
||||||
|
quote: this.name,
|
||||||
|
items: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const invoice = this.fyo.doc.getNewDoc(schemaName, data) as Invoice;
|
||||||
|
for (const row of this.items ?? []) {
|
||||||
|
await invoice.append('items', row.getValidDict(false, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!invoice.items?.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return invoice;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getListViewSettings(): ListViewSettings {
|
||||||
|
return {
|
||||||
|
columns: [
|
||||||
|
'name',
|
||||||
|
getTransactionStatusColumn(),
|
||||||
|
'party',
|
||||||
|
'date',
|
||||||
|
'baseGrandTotal',
|
||||||
|
'outstandingAmount',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getActions(fyo: Fyo): Action[] {
|
||||||
|
return getQuoteActions(fyo, ModelNameEnum.SalesQuote);
|
||||||
|
}
|
||||||
|
}
|
3
models/baseModels/SalesQuoteItem/SalesQuoteItem.ts
Normal file
3
models/baseModels/SalesQuoteItem/SalesQuoteItem.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { InvoiceItem } from '../InvoiceItem/InvoiceItem';
|
||||||
|
|
||||||
|
export class SalesQuoteItem extends InvoiceItem {}
|
@ -9,6 +9,7 @@ import { Invoice } from '../Invoice/Invoice';
|
|||||||
|
|
||||||
export class TaxSummary extends Doc {
|
export class TaxSummary extends Doc {
|
||||||
account?: string;
|
account?: string;
|
||||||
|
from_account?: string;
|
||||||
rate?: number;
|
rate?: number;
|
||||||
amount?: Money;
|
amount?: Money;
|
||||||
parentdoc?: Invoice;
|
parentdoc?: Invoice;
|
||||||
|
@ -11,10 +11,18 @@ import {
|
|||||||
} from './baseModels/Account/types';
|
} from './baseModels/Account/types';
|
||||||
import { numberSeriesDefaultsMap } from './baseModels/Defaults/Defaults';
|
import { numberSeriesDefaultsMap } from './baseModels/Defaults/Defaults';
|
||||||
import { Invoice } from './baseModels/Invoice/Invoice';
|
import { Invoice } from './baseModels/Invoice/Invoice';
|
||||||
|
import { SalesQuote } from './baseModels/SalesQuote/SalesQuote';
|
||||||
import { StockMovement } from './inventory/StockMovement';
|
import { StockMovement } from './inventory/StockMovement';
|
||||||
import { StockTransfer } from './inventory/StockTransfer';
|
import { StockTransfer } from './inventory/StockTransfer';
|
||||||
import { InvoiceStatus, ModelNameEnum } from './types';
|
import { InvoiceStatus, ModelNameEnum } from './types';
|
||||||
|
|
||||||
|
export function getQuoteActions(
|
||||||
|
fyo: Fyo,
|
||||||
|
schemaName: ModelNameEnum.SalesQuote
|
||||||
|
): Action[] {
|
||||||
|
return [getMakeInvoiceAction(fyo, schemaName)];
|
||||||
|
}
|
||||||
|
|
||||||
export function getInvoiceActions(
|
export function getInvoiceActions(
|
||||||
fyo: Fyo,
|
fyo: Fyo,
|
||||||
schemaName: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice
|
schemaName: ModelNameEnum.SalesInvoice | ModelNameEnum.PurchaseInvoice
|
||||||
@ -67,7 +75,10 @@ export function getMakeStockTransferAction(
|
|||||||
|
|
||||||
export function getMakeInvoiceAction(
|
export function getMakeInvoiceAction(
|
||||||
fyo: Fyo,
|
fyo: Fyo,
|
||||||
schemaName: ModelNameEnum.Shipment | ModelNameEnum.PurchaseReceipt
|
schemaName:
|
||||||
|
| ModelNameEnum.Shipment
|
||||||
|
| ModelNameEnum.PurchaseReceipt
|
||||||
|
| ModelNameEnum.SalesQuote
|
||||||
): Action {
|
): Action {
|
||||||
let label = fyo.t`Sales Invoice`;
|
let label = fyo.t`Sales Invoice`;
|
||||||
if (schemaName === ModelNameEnum.PurchaseReceipt) {
|
if (schemaName === ModelNameEnum.PurchaseReceipt) {
|
||||||
@ -77,9 +88,15 @@ export function getMakeInvoiceAction(
|
|||||||
return {
|
return {
|
||||||
label,
|
label,
|
||||||
group: fyo.t`Create`,
|
group: fyo.t`Create`,
|
||||||
condition: (doc: Doc) => doc.isSubmitted && !doc.backReference,
|
condition: (doc: Doc) => {
|
||||||
|
if (schemaName === ModelNameEnum.SalesQuote) {
|
||||||
|
return doc.isSubmitted;
|
||||||
|
} else {
|
||||||
|
return doc.isSubmitted && !doc.backReference;
|
||||||
|
}
|
||||||
|
},
|
||||||
action: async (doc: Doc) => {
|
action: async (doc: Doc) => {
|
||||||
const invoice = await (doc as StockTransfer).getInvoice();
|
const invoice = await (doc as SalesQuote | StockTransfer).getInvoice();
|
||||||
if (!invoice || !invoice.name) {
|
if (!invoice || !invoice.name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ import { PurchaseInvoice } from './baseModels/PurchaseInvoice/PurchaseInvoice';
|
|||||||
import { PurchaseInvoiceItem } from './baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem';
|
import { PurchaseInvoiceItem } from './baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem';
|
||||||
import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice';
|
import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice';
|
||||||
import { SalesInvoiceItem } from './baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
import { SalesInvoiceItem } from './baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||||
|
import { SalesQuote } from './baseModels/SalesQuote/SalesQuote';
|
||||||
|
import { SalesQuoteItem } from './baseModels/SalesQuoteItem/SalesQuoteItem';
|
||||||
import { SetupWizard } from './baseModels/SetupWizard/SetupWizard';
|
import { SetupWizard } from './baseModels/SetupWizard/SetupWizard';
|
||||||
import { Tax } from './baseModels/Tax/Tax';
|
import { Tax } from './baseModels/Tax/Tax';
|
||||||
import { TaxSummary } from './baseModels/TaxSummary/TaxSummary';
|
import { TaxSummary } from './baseModels/TaxSummary/TaxSummary';
|
||||||
@ -33,6 +35,12 @@ import { ShipmentItem } from './inventory/ShipmentItem';
|
|||||||
import { StockLedgerEntry } from './inventory/StockLedgerEntry';
|
import { StockLedgerEntry } from './inventory/StockLedgerEntry';
|
||||||
import { StockMovement } from './inventory/StockMovement';
|
import { StockMovement } from './inventory/StockMovement';
|
||||||
import { StockMovementItem } from './inventory/StockMovementItem';
|
import { StockMovementItem } from './inventory/StockMovementItem';
|
||||||
|
import { ClosingAmounts } from './inventory/Point of Sale/ClosingAmounts';
|
||||||
|
import { ClosingCash } from './inventory/Point of Sale/ClosingCash';
|
||||||
|
import { OpeningAmounts } from './inventory/Point of Sale/OpeningAmounts';
|
||||||
|
import { OpeningCash } from './inventory/Point of Sale/OpeningCash';
|
||||||
|
import { POSSettings } from './inventory/Point of Sale/POSSettings';
|
||||||
|
import { POSShift } from './inventory/Point of Sale/POSShift';
|
||||||
|
|
||||||
export const models = {
|
export const models = {
|
||||||
Account,
|
Account,
|
||||||
@ -55,6 +63,8 @@ export const models = {
|
|||||||
PurchaseInvoiceItem,
|
PurchaseInvoiceItem,
|
||||||
SalesInvoice,
|
SalesInvoice,
|
||||||
SalesInvoiceItem,
|
SalesInvoiceItem,
|
||||||
|
SalesQuote,
|
||||||
|
SalesQuoteItem,
|
||||||
SerialNumber,
|
SerialNumber,
|
||||||
SetupWizard,
|
SetupWizard,
|
||||||
PrintTemplate,
|
PrintTemplate,
|
||||||
@ -70,6 +80,13 @@ export const models = {
|
|||||||
ShipmentItem,
|
ShipmentItem,
|
||||||
PurchaseReceipt,
|
PurchaseReceipt,
|
||||||
PurchaseReceiptItem,
|
PurchaseReceiptItem,
|
||||||
|
// POS Models
|
||||||
|
ClosingAmounts,
|
||||||
|
ClosingCash,
|
||||||
|
OpeningAmounts,
|
||||||
|
OpeningCash,
|
||||||
|
POSSettings,
|
||||||
|
POSShift,
|
||||||
} as ModelMap;
|
} as ModelMap;
|
||||||
|
|
||||||
export async function getRegionalModels(
|
export async function getRegionalModels(
|
||||||
|
@ -12,6 +12,7 @@ export class InventorySettings extends Doc {
|
|||||||
enableSerialNumber?: boolean;
|
enableSerialNumber?: boolean;
|
||||||
enableUomConversions?: boolean;
|
enableUomConversions?: boolean;
|
||||||
enableStockReturns?: boolean;
|
enableStockReturns?: boolean;
|
||||||
|
enablePointOfSale?: boolean;
|
||||||
|
|
||||||
static filters: FiltersMap = {
|
static filters: FiltersMap = {
|
||||||
stockInHand: () => ({
|
stockInHand: () => ({
|
||||||
@ -44,5 +45,8 @@ export class InventorySettings extends Doc {
|
|||||||
enableStockReturns: () => {
|
enableStockReturns: () => {
|
||||||
return !!this.enableStockReturns;
|
return !!this.enableStockReturns;
|
||||||
},
|
},
|
||||||
|
enablePointOfSale: () => {
|
||||||
|
return !!this.fyo.singles.POSShift?.isShiftOpen;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
6
models/inventory/Point of Sale/CashDenominations.ts
Normal file
6
models/inventory/Point of Sale/CashDenominations.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { Doc } from 'fyo/model/doc';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
|
||||||
|
export abstract class CashDenominations extends Doc {
|
||||||
|
denomination?: Money;
|
||||||
|
}
|
27
models/inventory/Point of Sale/ClosingAmounts.ts
Normal file
27
models/inventory/Point of Sale/ClosingAmounts.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Doc } from 'fyo/model/doc';
|
||||||
|
import { FormulaMap } from 'fyo/model/types';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
|
||||||
|
export class ClosingAmounts extends Doc {
|
||||||
|
closingAmount?: Money;
|
||||||
|
differenceAmount?: Money;
|
||||||
|
expectedAmount?: Money;
|
||||||
|
openingAmount?: Money;
|
||||||
|
paymentMethod?: string;
|
||||||
|
|
||||||
|
formulas: FormulaMap = {
|
||||||
|
differenceAmount: {
|
||||||
|
formula: () => {
|
||||||
|
if (!this.closingAmount) {
|
||||||
|
return this.fyo.pesa(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.expectedAmount) {
|
||||||
|
return this.fyo.pesa(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.closingAmount.sub(this.expectedAmount);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
5
models/inventory/Point of Sale/ClosingCash.ts
Normal file
5
models/inventory/Point of Sale/ClosingCash.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { CashDenominations } from './CashDenominations';
|
||||||
|
|
||||||
|
export class ClosingCash extends CashDenominations {
|
||||||
|
count?: number;
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
import { CashDenominations } from './CashDenominations';
|
||||||
|
|
||||||
|
export class DefaultCashDenominations extends CashDenominations {}
|
11
models/inventory/Point of Sale/OpeningAmounts.ts
Normal file
11
models/inventory/Point of Sale/OpeningAmounts.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Doc } from 'fyo/model/doc';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
|
||||||
|
export class OpeningAmounts extends Doc {
|
||||||
|
amount?: Money;
|
||||||
|
paymentMethod?: 'Cash' | 'Transfer';
|
||||||
|
|
||||||
|
get openingCashAmount() {
|
||||||
|
return this.parentdoc?.openingCashAmount as Money;
|
||||||
|
}
|
||||||
|
}
|
5
models/inventory/Point of Sale/OpeningCash.ts
Normal file
5
models/inventory/Point of Sale/OpeningCash.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { CashDenominations } from './CashDenominations';
|
||||||
|
|
||||||
|
export class OpeningCash extends CashDenominations {
|
||||||
|
count?: number;
|
||||||
|
}
|
19
models/inventory/Point of Sale/POSSettings.ts
Normal file
19
models/inventory/Point of Sale/POSSettings.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Doc } from 'fyo/model/doc';
|
||||||
|
import { FiltersMap } from 'fyo/model/types';
|
||||||
|
import {
|
||||||
|
AccountRootTypeEnum,
|
||||||
|
AccountTypeEnum,
|
||||||
|
} from 'models/baseModels/Account/types';
|
||||||
|
|
||||||
|
export class POSSettings extends Doc {
|
||||||
|
inventory?: string;
|
||||||
|
cashAccount?: string;
|
||||||
|
writeOffAccount?: string;
|
||||||
|
|
||||||
|
static filters: FiltersMap = {
|
||||||
|
cashAccount: () => ({
|
||||||
|
rootType: AccountRootTypeEnum.Asset,
|
||||||
|
accountType: AccountTypeEnum.Cash,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
61
models/inventory/Point of Sale/POSShift.ts
Normal file
61
models/inventory/Point of Sale/POSShift.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { ClosingAmounts } from './ClosingAmounts';
|
||||||
|
import { ClosingCash } from './ClosingCash';
|
||||||
|
import { Doc } from 'fyo/model/doc';
|
||||||
|
import { OpeningAmounts } from './OpeningAmounts';
|
||||||
|
import { OpeningCash } from './OpeningCash';
|
||||||
|
|
||||||
|
export class POSShift extends Doc {
|
||||||
|
closingAmounts?: ClosingAmounts[];
|
||||||
|
closingCash?: ClosingCash[];
|
||||||
|
closingDate?: Date;
|
||||||
|
isShiftOpen?: boolean;
|
||||||
|
openingAmounts?: OpeningAmounts[];
|
||||||
|
openingCash?: OpeningCash[];
|
||||||
|
openingDate?: Date;
|
||||||
|
|
||||||
|
get openingCashAmount() {
|
||||||
|
if (!this.openingCash) {
|
||||||
|
return this.fyo.pesa(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let openingAmount = this.fyo.pesa(0);
|
||||||
|
|
||||||
|
this.openingCash.map((row: OpeningCash) => {
|
||||||
|
const denomination = row.denomination ?? this.fyo.pesa(0);
|
||||||
|
const count = row.count ?? 0;
|
||||||
|
|
||||||
|
const amount = denomination.mul(count);
|
||||||
|
openingAmount = openingAmount.add(amount);
|
||||||
|
});
|
||||||
|
return openingAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
get closingCashAmount() {
|
||||||
|
if (!this.closingCash) {
|
||||||
|
return this.fyo.pesa(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let closingAmount = this.fyo.pesa(0);
|
||||||
|
|
||||||
|
this.closingCash.map((row: ClosingCash) => {
|
||||||
|
const denomination = row.denomination ?? this.fyo.pesa(0);
|
||||||
|
const count = row.count ?? 0;
|
||||||
|
|
||||||
|
const amount = denomination.mul(count);
|
||||||
|
closingAmount = closingAmount.add(amount);
|
||||||
|
});
|
||||||
|
return closingAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
get openingTransferAmount() {
|
||||||
|
if (!this.openingAmounts) {
|
||||||
|
return this.fyo.pesa(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transferAmountRow = this.openingAmounts.filter(
|
||||||
|
(row) => row.paymentMethod === 'Transfer'
|
||||||
|
)[0];
|
||||||
|
|
||||||
|
return transferAmountRow.amount ?? this.fyo.pesa(0);
|
||||||
|
}
|
||||||
|
}
|
103
models/inventory/Point of Sale/tests/testPointOfSale.spec.ts
Normal file
103
models/inventory/Point of Sale/tests/testPointOfSale.spec.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import test from 'tape';
|
||||||
|
import { closeTestFyo, getTestFyo, setupTestFyo } from 'tests/helpers';
|
||||||
|
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||||
|
import { Payment } from 'models/baseModels/Payment/Payment';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
|
||||||
|
const fyo = getTestFyo();
|
||||||
|
|
||||||
|
setupTestFyo(fyo, __filename);
|
||||||
|
|
||||||
|
const customer = { name: 'Someone', role: 'Both' };
|
||||||
|
const itemMap = {
|
||||||
|
Pen: {
|
||||||
|
name: 'Pen',
|
||||||
|
rate: 700,
|
||||||
|
},
|
||||||
|
Ink: {
|
||||||
|
name: 'Ink',
|
||||||
|
rate: 50,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test('insert test docs', async (t) => {
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.Item, itemMap.Pen).sync();
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.Item, itemMap.Ink).sync();
|
||||||
|
await fyo.doc.getNewDoc(ModelNameEnum.Party, customer).sync();
|
||||||
|
});
|
||||||
|
|
||||||
|
let sinvDocOne: SalesInvoice | undefined;
|
||||||
|
|
||||||
|
test('check pos transacted amount', async (t) => {
|
||||||
|
const transactedAmountBeforeTxn = await fyo.db.getPOSTransactedAmount(
|
||||||
|
new Date('2023-01-01'),
|
||||||
|
new Date('2023-01-02')
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equals(transactedAmountBeforeTxn, undefined);
|
||||||
|
|
||||||
|
sinvDocOne = fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
|
||||||
|
isPOS: true,
|
||||||
|
date: new Date('2023-01-01'),
|
||||||
|
account: 'Debtors',
|
||||||
|
party: customer.name,
|
||||||
|
}) as SalesInvoice;
|
||||||
|
|
||||||
|
await sinvDocOne.append('items', {
|
||||||
|
item: itemMap.Pen.name,
|
||||||
|
rate: itemMap.Pen.rate,
|
||||||
|
quantity: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
await (await sinvDocOne.sync()).submit();
|
||||||
|
const paymentDocOne = sinvDocOne.getPayment() as Payment;
|
||||||
|
|
||||||
|
await paymentDocOne.sync();
|
||||||
|
|
||||||
|
const sinvDocTwo = fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
|
||||||
|
isPOS: true,
|
||||||
|
date: new Date('2023-01-01'),
|
||||||
|
account: 'Debtors',
|
||||||
|
party: customer.name,
|
||||||
|
}) as SalesInvoice;
|
||||||
|
|
||||||
|
await sinvDocTwo.append('items', {
|
||||||
|
item: itemMap.Pen.name,
|
||||||
|
rate: itemMap.Pen.rate,
|
||||||
|
quantity: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
await (await sinvDocTwo.sync()).submit();
|
||||||
|
const paymentDocTwo = sinvDocTwo.getPayment() as Payment;
|
||||||
|
|
||||||
|
await paymentDocTwo.setMultiple({
|
||||||
|
paymentMethod: 'Transfer',
|
||||||
|
clearanceDate: new Date('2023-01-01'),
|
||||||
|
referenceId: 'xxxxxxxx',
|
||||||
|
});
|
||||||
|
|
||||||
|
await paymentDocTwo.sync();
|
||||||
|
|
||||||
|
const transactedAmountAfterTxn: Record<string, Money> | undefined =
|
||||||
|
await fyo.db.getPOSTransactedAmount(
|
||||||
|
new Date('2023-01-01'),
|
||||||
|
new Date('2023-01-02')
|
||||||
|
);
|
||||||
|
|
||||||
|
t.true(transactedAmountAfterTxn);
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
transactedAmountAfterTxn?.Cash,
|
||||||
|
sinvDocOne.grandTotal?.float,
|
||||||
|
'transacted cash amount matches'
|
||||||
|
);
|
||||||
|
|
||||||
|
t.equals(
|
||||||
|
transactedAmountAfterTxn?.Transfer,
|
||||||
|
sinvDocTwo.grandTotal?.float,
|
||||||
|
'transacted transfer amount matches'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
closeTestFyo(fyo, __filename);
|
@ -27,6 +27,8 @@ export enum ModelNameEnum {
|
|||||||
PurchaseInvoiceItem = 'PurchaseInvoiceItem',
|
PurchaseInvoiceItem = 'PurchaseInvoiceItem',
|
||||||
SalesInvoice = 'SalesInvoice',
|
SalesInvoice = 'SalesInvoice',
|
||||||
SalesInvoiceItem = 'SalesInvoiceItem',
|
SalesInvoiceItem = 'SalesInvoiceItem',
|
||||||
|
SalesQuote = 'SalesQuote',
|
||||||
|
SalesQuoteItem = 'SalesQuoteItem',
|
||||||
SerialNumber = 'SerialNumber',
|
SerialNumber = 'SerialNumber',
|
||||||
SetupWizard = 'SetupWizard',
|
SetupWizard = 'SetupWizard',
|
||||||
Tax = 'Tax',
|
Tax = 'Tax',
|
||||||
@ -45,7 +47,9 @@ export enum ModelNameEnum {
|
|||||||
PurchaseReceiptItem = 'PurchaseReceiptItem',
|
PurchaseReceiptItem = 'PurchaseReceiptItem',
|
||||||
Location = 'Location',
|
Location = 'Location',
|
||||||
CustomForm = 'CustomForm',
|
CustomForm = 'CustomForm',
|
||||||
CustomField = 'CustomField'
|
CustomField = 'CustomField',
|
||||||
|
POSSettings = 'POSSettings',
|
||||||
|
POSShift = 'POSShift'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModelName = keyof typeof ModelNameEnum;
|
export type ModelName = keyof typeof ModelNameEnum;
|
||||||
|
17
package.json
17
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "frappe-books",
|
"name": "frappe-books",
|
||||||
"version": "0.19.0",
|
"version": "0.21.2",
|
||||||
"description": "Simple book-keeping app for everyone",
|
"description": "Simple book-keeping app for everyone",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Frappe Technologies Pvt. Ltd.",
|
"name": "Frappe Technologies Pvt. Ltd.",
|
||||||
@ -23,7 +23,7 @@
|
|||||||
"@codemirror/autocomplete": "^6.4.2",
|
"@codemirror/autocomplete": "^6.4.2",
|
||||||
"@codemirror/lang-vue": "^0.1.1",
|
"@codemirror/lang-vue": "^0.1.1",
|
||||||
"@popperjs/core": "^2.10.2",
|
"@popperjs/core": "^2.10.2",
|
||||||
"better-sqlite3": "^7.5.3",
|
"better-sqlite3": "^9.2.2",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"core-js": "^3.19.0",
|
"core-js": "^3.19.0",
|
||||||
"electron-store": "^8.0.1",
|
"electron-store": "^8.0.1",
|
||||||
@ -41,6 +41,7 @@
|
|||||||
"@codemirror/language": "^6.0.0",
|
"@codemirror/language": "^6.0.0",
|
||||||
"@codemirror/state": "^6.0.0",
|
"@codemirror/state": "^6.0.0",
|
||||||
"@codemirror/view": "^6.0.0",
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"@electron/rebuild": "^3.4.1",
|
||||||
"@lezer/common": "^1.0.0",
|
"@lezer/common": "^1.0.0",
|
||||||
"@types/assert": "^1.5.6",
|
"@types/assert": "^1.5.6",
|
||||||
"@types/better-sqlite3": "^7.6.4",
|
"@types/better-sqlite3": "^7.6.4",
|
||||||
@ -56,11 +57,10 @@
|
|||||||
"autoprefixer": "^9",
|
"autoprefixer": "^9",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"electron": "18.3.7",
|
"electron": "22.3.27",
|
||||||
"electron-builder": "^24.4.0",
|
"electron-builder": "^24.9.1",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
"electron-rebuild": "^3.2.9",
|
"electron-updater": "^6.1.7",
|
||||||
"electron-updater": "^5.2.1",
|
|
||||||
"eslint": "^8.43.0",
|
"eslint": "^8.43.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
@ -78,10 +78,13 @@
|
|||||||
"tsconfig-paths": "^3.14.1",
|
"tsconfig-paths": "^3.14.1",
|
||||||
"tslib": "^2.3.1",
|
"tslib": "^2.3.1",
|
||||||
"typescript": "^4.6.2",
|
"typescript": "^4.6.2",
|
||||||
"vite": "^4.3.9",
|
"vite": "^4.5.2",
|
||||||
"vue-tsc": "^1.6.5",
|
"vue-tsc": "^1.6.5",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"node-abi": "^3.54.0"
|
||||||
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
|
@ -64,12 +64,12 @@ export abstract class AccountReport extends LedgerReport {
|
|||||||
this._dateRanges = await this._getDateRanges();
|
this._dateRanges = await this._getDateRanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
getRootNode(
|
getRootNodes(
|
||||||
rootType: AccountRootType,
|
rootType: AccountRootType,
|
||||||
accountTree: AccountTree
|
accountTree: AccountTree
|
||||||
): AccountTreeNode | undefined {
|
): AccountTreeNode[] | undefined {
|
||||||
const rootNodeList = Object.values(accountTree);
|
const rootNodeList = Object.values(accountTree);
|
||||||
return rootNodeList.find((n) => n.rootType === rootType);
|
return rootNodeList.filter((n) => n.rootType === rootType);
|
||||||
}
|
}
|
||||||
|
|
||||||
getEmptyRow(): ReportRow {
|
getEmptyRow(): ReportRow {
|
||||||
@ -88,8 +88,11 @@ export abstract class AccountReport extends LedgerReport {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getTotalNode(rootNode: AccountTreeNode, name: string): AccountListNode {
|
getTotalNode(rootNodes: AccountTreeNode[], name: string): AccountListNode {
|
||||||
const accountTree = { [rootNode.name]: rootNode };
|
const accountTree: Tree = {};
|
||||||
|
for (const rootNode of rootNodes) {
|
||||||
|
accountTree[rootNode.name] = rootNode;
|
||||||
|
}
|
||||||
const leafNodes = getListOfLeafNodes(accountTree) as AccountTreeNode[];
|
const leafNodes = getListOfLeafNodes(accountTree) as AccountTreeNode[];
|
||||||
|
|
||||||
const totalMap = leafNodes.reduce((acc, node) => {
|
const totalMap = leafNodes.reduce((acc, node) => {
|
||||||
@ -236,6 +239,17 @@ export abstract class AccountReport extends LedgerReport {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fix arythmetic on dates when adding or substracting months. If the
|
||||||
|
// reference date was the last day in month, ensure that the resulting date is
|
||||||
|
// also the last day.
|
||||||
|
_fixMonthsJump(refDate: DateTime, date: DateTime): DateTime {
|
||||||
|
if (refDate.day == refDate.daysInMonth && date.day != date.daysInMonth) {
|
||||||
|
return date.set({ day: date.daysInMonth });
|
||||||
|
} else {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async _getDateRanges(): Promise<DateRange[]> {
|
async _getDateRanges(): Promise<DateRange[]> {
|
||||||
const endpoints = await this._getFromAndToDates();
|
const endpoints = await this._getFromAndToDates();
|
||||||
const fromDate = DateTime.fromISO(endpoints.fromDate);
|
const fromDate = DateTime.fromISO(endpoints.fromDate);
|
||||||
@ -252,7 +266,10 @@ export abstract class AccountReport extends LedgerReport {
|
|||||||
|
|
||||||
const months: number = monthsMap[this.periodicity];
|
const months: number = monthsMap[this.periodicity];
|
||||||
const dateRanges: DateRange[] = [
|
const dateRanges: DateRange[] = [
|
||||||
{ toDate, fromDate: toDate.minus({ months }) },
|
{
|
||||||
|
toDate,
|
||||||
|
fromDate: this._fixMonthsJump(toDate, toDate.minus({ months })),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let count = this.count ?? 1;
|
let count = this.count ?? 1;
|
||||||
@ -264,7 +281,10 @@ export abstract class AccountReport extends LedgerReport {
|
|||||||
const lastRange = dateRanges.at(-1)!;
|
const lastRange = dateRanges.at(-1)!;
|
||||||
dateRanges.push({
|
dateRanges.push({
|
||||||
toDate: lastRange.fromDate,
|
toDate: lastRange.fromDate,
|
||||||
fromDate: lastRange.fromDate.minus({ months }),
|
fromDate: this._fixMonthsJump(
|
||||||
|
toDate,
|
||||||
|
lastRange.fromDate.minus({ months })
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -445,14 +465,15 @@ export async function getFiscalEndpoints(
|
|||||||
|
|
||||||
const fromDate = [
|
const fromDate = [
|
||||||
fromYear,
|
fromYear,
|
||||||
fys.toISOString().split('T')[0].split('-').slice(1),
|
(fys.getMonth() + 1).toString().padStart(2, '0'),
|
||||||
]
|
fys.getDate().toString().padStart(2, '0'),
|
||||||
.flat()
|
].join('-');
|
||||||
.join('-');
|
|
||||||
|
|
||||||
const toDate = [toYear, fye.toISOString().split('T')[0].split('-').slice(1)]
|
const toDate = [
|
||||||
.flat()
|
toYear,
|
||||||
.join('-');
|
(fye.getMonth() + 1).toString().padStart(2, '0'),
|
||||||
|
fye.getDate().toString().padStart(2, '0'),
|
||||||
|
].join('-');
|
||||||
|
|
||||||
return { fromDate, toDate };
|
return { fromDate, toDate };
|
||||||
}
|
}
|
||||||
@ -573,15 +594,17 @@ function getPrunedChildren(children: AccountTreeNode[]): AccountTreeNode[] {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertAccountRootNodeToAccountList(
|
export function convertAccountRootNodesToAccountList(
|
||||||
rootNode: AccountTreeNode
|
rootNodes: AccountTreeNode[]
|
||||||
): AccountList {
|
): AccountList {
|
||||||
if (!rootNode) {
|
if (!rootNodes || rootNodes.length == 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountList: AccountList = [];
|
const accountList: AccountList = [];
|
||||||
|
for (const rootNode of rootNodes) {
|
||||||
pushToAccountList(rootNode, accountList, 0);
|
pushToAccountList(rootNode, accountList, 0);
|
||||||
|
}
|
||||||
return accountList;
|
return accountList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
} from 'models/baseModels/Account/types';
|
} from 'models/baseModels/Account/types';
|
||||||
import {
|
import {
|
||||||
AccountReport,
|
AccountReport,
|
||||||
convertAccountRootNodeToAccountList,
|
convertAccountRootNodesToAccountList,
|
||||||
} from 'reports/AccountReport';
|
} from 'reports/AccountReport';
|
||||||
import { ReportData, RootTypeRow } from 'reports/types';
|
import { ReportData, RootTypeRow } from 'reports/types';
|
||||||
import { getMapFromList } from 'utils';
|
import { getMapFromList } from 'utils';
|
||||||
@ -44,15 +44,15 @@ export class BalanceSheet extends AccountReport {
|
|||||||
|
|
||||||
const rootTypeRows: RootTypeRow[] = this.rootTypes
|
const rootTypeRows: RootTypeRow[] = this.rootTypes
|
||||||
.map((rootType) => {
|
.map((rootType) => {
|
||||||
const rootNode = this.getRootNode(rootType, accountTree)!;
|
const rootNodes = this.getRootNodes(rootType, accountTree)!;
|
||||||
const rootList = convertAccountRootNodeToAccountList(rootNode);
|
const rootList = convertAccountRootNodesToAccountList(rootNodes);
|
||||||
return {
|
return {
|
||||||
rootType,
|
rootType,
|
||||||
rootNode,
|
rootNodes,
|
||||||
rows: this.getReportRowsFromAccountList(rootList),
|
rows: this.getReportRowsFromAccountList(rootList),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((row) => !!row.rootNode);
|
.filter((row) => !!row.rootNodes.length);
|
||||||
|
|
||||||
this.reportData = this.getReportDataFromRows(
|
this.reportData = this.getReportDataFromRows(
|
||||||
getMapFromList(rootTypeRows, 'rootType')
|
getMapFromList(rootTypeRows, 'rootType')
|
||||||
@ -88,8 +88,8 @@ export class BalanceSheet extends AccountReport {
|
|||||||
|
|
||||||
reportData.push(...row.rows);
|
reportData.push(...row.rows);
|
||||||
|
|
||||||
if (row.rootNode) {
|
if (row.rootNodes.length) {
|
||||||
const totalNode = this.getTotalNode(row.rootNode, totalName);
|
const totalNode = this.getTotalNode(row.rootNodes, totalName);
|
||||||
const totalRow = this.getRowFromAccountListNode(totalNode);
|
const totalRow = this.getRowFromAccountListNode(totalNode);
|
||||||
reportData.push(totalRow);
|
reportData.push(totalRow);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
} from 'models/baseModels/Account/types';
|
} from 'models/baseModels/Account/types';
|
||||||
import {
|
import {
|
||||||
AccountReport,
|
AccountReport,
|
||||||
convertAccountRootNodeToAccountList,
|
convertAccountRootNodesToAccountList,
|
||||||
} from 'reports/AccountReport';
|
} from 'reports/AccountReport';
|
||||||
import {
|
import {
|
||||||
AccountListNode,
|
AccountListNode,
|
||||||
@ -45,28 +45,28 @@ export class ProfitAndLoss extends AccountReport {
|
|||||||
/**
|
/**
|
||||||
* Income Rows
|
* Income Rows
|
||||||
*/
|
*/
|
||||||
const incomeRoot = this.getRootNode(
|
const incomeRoots = this.getRootNodes(
|
||||||
AccountRootTypeEnum.Income,
|
AccountRootTypeEnum.Income,
|
||||||
accountTree
|
accountTree
|
||||||
)!;
|
)!;
|
||||||
const incomeList = convertAccountRootNodeToAccountList(incomeRoot);
|
const incomeList = convertAccountRootNodesToAccountList(incomeRoots);
|
||||||
const incomeRows = this.getReportRowsFromAccountList(incomeList);
|
const incomeRows = this.getReportRowsFromAccountList(incomeList);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expense Rows
|
* Expense Rows
|
||||||
*/
|
*/
|
||||||
const expenseRoot = this.getRootNode(
|
const expenseRoots = this.getRootNodes(
|
||||||
AccountRootTypeEnum.Expense,
|
AccountRootTypeEnum.Expense,
|
||||||
accountTree
|
accountTree
|
||||||
)!;
|
)!;
|
||||||
const expenseList = convertAccountRootNodeToAccountList(expenseRoot);
|
const expenseList = convertAccountRootNodesToAccountList(expenseRoots);
|
||||||
const expenseRows = this.getReportRowsFromAccountList(expenseList);
|
const expenseRows = this.getReportRowsFromAccountList(expenseList);
|
||||||
|
|
||||||
this.reportData = this.getReportDataFromRows(
|
this.reportData = this.getReportDataFromRows(
|
||||||
incomeRows,
|
incomeRows,
|
||||||
expenseRows,
|
expenseRows,
|
||||||
incomeRoot,
|
incomeRoots,
|
||||||
expenseRoot
|
expenseRoots
|
||||||
);
|
);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@ -74,43 +74,57 @@ export class ProfitAndLoss extends AccountReport {
|
|||||||
getReportDataFromRows(
|
getReportDataFromRows(
|
||||||
incomeRows: ReportData,
|
incomeRows: ReportData,
|
||||||
expenseRows: ReportData,
|
expenseRows: ReportData,
|
||||||
incomeRoot: AccountTreeNode | undefined,
|
incomeRoots: AccountTreeNode[] | undefined,
|
||||||
expenseRoot: AccountTreeNode | undefined
|
expenseRoots: AccountTreeNode[] | undefined
|
||||||
): ReportData {
|
): ReportData {
|
||||||
if (incomeRoot && !expenseRoot) {
|
if (
|
||||||
|
incomeRoots &&
|
||||||
|
incomeRoots.length &&
|
||||||
|
!expenseRoots &&
|
||||||
|
!expenseRoots.length
|
||||||
|
) {
|
||||||
return this.getIncomeOrExpenseRows(
|
return this.getIncomeOrExpenseRows(
|
||||||
incomeRoot,
|
incomeRoots,
|
||||||
incomeRows,
|
incomeRows,
|
||||||
t`Total Income (Credit)`
|
t`Total Income (Credit)`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expenseRoot && !incomeRoot) {
|
if (
|
||||||
|
expenseRoots &&
|
||||||
|
expenseRoots.length &&
|
||||||
|
(!incomeRoots || !incomeRoots.length)
|
||||||
|
) {
|
||||||
return this.getIncomeOrExpenseRows(
|
return this.getIncomeOrExpenseRows(
|
||||||
expenseRoot,
|
expenseRoots,
|
||||||
expenseRows,
|
expenseRows,
|
||||||
t`Total Income (Credit)`
|
t`Total Income (Credit)`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!incomeRoot || !expenseRoot) {
|
if (
|
||||||
|
!incomeRoots ||
|
||||||
|
!incomeRoots.length ||
|
||||||
|
!expenseRoots ||
|
||||||
|
!expenseRoots.length
|
||||||
|
) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getIncomeAndExpenseRows(
|
return this.getIncomeAndExpenseRows(
|
||||||
incomeRows,
|
incomeRows,
|
||||||
expenseRows,
|
expenseRows,
|
||||||
incomeRoot,
|
incomeRoots,
|
||||||
expenseRoot
|
expenseRoots
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getIncomeOrExpenseRows(
|
getIncomeOrExpenseRows(
|
||||||
root: AccountTreeNode,
|
roots: AccountTreeNode[],
|
||||||
rows: ReportData,
|
rows: ReportData,
|
||||||
totalRowName: string
|
totalRowName: string
|
||||||
): ReportData {
|
): ReportData {
|
||||||
const total = this.getTotalNode(root, totalRowName);
|
const total = this.getTotalNode(roots, totalRowName);
|
||||||
const totalRow = this.getRowFromAccountListNode(total);
|
const totalRow = this.getRowFromAccountListNode(total);
|
||||||
|
|
||||||
return [rows, totalRow].flat();
|
return [rows, totalRow].flat();
|
||||||
@ -119,14 +133,17 @@ export class ProfitAndLoss extends AccountReport {
|
|||||||
getIncomeAndExpenseRows(
|
getIncomeAndExpenseRows(
|
||||||
incomeRows: ReportData,
|
incomeRows: ReportData,
|
||||||
expenseRows: ReportData,
|
expenseRows: ReportData,
|
||||||
incomeRoot: AccountTreeNode,
|
incomeRoots: AccountTreeNode[],
|
||||||
expenseRoot: AccountTreeNode
|
expenseRoots: AccountTreeNode[]
|
||||||
) {
|
) {
|
||||||
const totalIncome = this.getTotalNode(incomeRoot, t`Total Income (Credit)`);
|
const totalIncome = this.getTotalNode(
|
||||||
|
incomeRoots,
|
||||||
|
t`Total Income (Credit)`
|
||||||
|
);
|
||||||
const totalIncomeRow = this.getRowFromAccountListNode(totalIncome);
|
const totalIncomeRow = this.getRowFromAccountListNode(totalIncome);
|
||||||
|
|
||||||
const totalExpense = this.getTotalNode(
|
const totalExpense = this.getTotalNode(
|
||||||
expenseRoot,
|
expenseRoots,
|
||||||
t`Total Expense (Debit)`
|
t`Total Expense (Debit)`
|
||||||
);
|
);
|
||||||
const totalExpenseRow = this.getRowFromAccountListNode(totalExpense);
|
const totalExpenseRow = this.getRowFromAccountListNode(totalExpense);
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
AccountReport,
|
AccountReport,
|
||||||
ACC_BAL_WIDTH,
|
ACC_BAL_WIDTH,
|
||||||
ACC_NAME_WIDTH,
|
ACC_NAME_WIDTH,
|
||||||
convertAccountRootNodeToAccountList,
|
convertAccountRootNodesToAccountList,
|
||||||
getFiscalEndpoints,
|
getFiscalEndpoints,
|
||||||
} from 'reports/AccountReport';
|
} from 'reports/AccountReport';
|
||||||
import {
|
import {
|
||||||
@ -65,15 +65,15 @@ export class TrialBalance extends AccountReport {
|
|||||||
|
|
||||||
const rootTypeRows: RootTypeRow[] = this.rootTypes
|
const rootTypeRows: RootTypeRow[] = this.rootTypes
|
||||||
.map((rootType) => {
|
.map((rootType) => {
|
||||||
const rootNode = this.getRootNode(rootType, accountTree)!;
|
const rootNodes = this.getRootNodes(rootType, accountTree)!;
|
||||||
const rootList = convertAccountRootNodeToAccountList(rootNode);
|
const rootList = convertAccountRootNodesToAccountList(rootNodes);
|
||||||
return {
|
return {
|
||||||
rootType,
|
rootType,
|
||||||
rootNode,
|
rootNodes,
|
||||||
rows: this.getReportRowsFromAccountList(rootList),
|
rows: this.getReportRowsFromAccountList(rootList),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((row) => !!row.rootNode);
|
.filter((row) => !!(row.rootNodes && row.rootNodes.length));
|
||||||
|
|
||||||
this.reportData = await this.getReportDataFromRows(rootTypeRows);
|
this.reportData = await this.getReportDataFromRows(rootTypeRows);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
@ -107,6 +107,6 @@ export type Tree = Record<string, TreeNode>;
|
|||||||
|
|
||||||
export type RootTypeRow = {
|
export type RootTypeRow = {
|
||||||
rootType: AccountRootType;
|
rootType: AccountRootType;
|
||||||
rootNode: AccountTreeNode;
|
rootNodes: AccountTreeNode[];
|
||||||
rows: ReportData;
|
rows: ReportData;
|
||||||
};
|
};
|
@ -92,6 +92,14 @@
|
|||||||
"create": true,
|
"create": true,
|
||||||
"section": "Number Series"
|
"section": "Number Series"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "salesQuoteNumberSeries",
|
||||||
|
"label": "Sales Quote Number Series",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "NumberSeries",
|
||||||
|
"create": true,
|
||||||
|
"section": "Number Series"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "salesInvoiceTerms",
|
"fieldname": "salesInvoiceTerms",
|
||||||
"label": "Sales Invoice Terms",
|
"label": "Sales Invoice Terms",
|
||||||
@ -116,6 +124,13 @@
|
|||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"section": "Terms"
|
"section": "Terms"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "salesQuotePrintTemplate",
|
||||||
|
"label": "Sales Quote Print Template",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "PrintTemplate",
|
||||||
|
"section": "Print Templates"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "salesInvoicePrintTemplate",
|
"fieldname": "salesInvoicePrintTemplate",
|
||||||
"label": "Sales Invoice Print Template",
|
"label": "Sales Invoice Print Template",
|
||||||
@ -164,6 +179,21 @@
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"target": "PrintTemplate",
|
"target": "PrintTemplate",
|
||||||
"section": "Print Templates"
|
"section": "Print Templates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posCustomer",
|
||||||
|
"label": "POS Customer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Party",
|
||||||
|
"create": true,
|
||||||
|
"section": "Point of Sale"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "posCashDenominations",
|
||||||
|
"label": "Cash Denominations",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"target": "DefaultCashDenominations",
|
||||||
|
"section": "Point of Sale"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,10 @@
|
|||||||
"value": "SalesInvoice",
|
"value": "SalesInvoice",
|
||||||
"label": "Sales Invoice"
|
"label": "Sales Invoice"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"value": "SalesQuote",
|
||||||
|
"label": "Sales Quote"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"value": "PurchaseInvoice",
|
"value": "PurchaseInvoice",
|
||||||
"label": "Purchase Invoice"
|
"label": "Purchase Invoice"
|
||||||
|
@ -141,6 +141,14 @@
|
|||||||
"computed": true,
|
"computed": true,
|
||||||
"section": "Amounts"
|
"section": "Amounts"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "taxes",
|
||||||
|
"label": "Taxes",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"target": "TaxSummary",
|
||||||
|
"readOnly": true,
|
||||||
|
"section": "Amounts"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "for",
|
"fieldname": "for",
|
||||||
"label": "Payment Reference",
|
"label": "Payment Reference",
|
||||||
@ -171,8 +179,7 @@
|
|||||||
"label": "Purchase"
|
"label": "Purchase"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"hidden": true,
|
"hidden": true
|
||||||
"required": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quickEditFields": [
|
"quickEditFields": [
|
||||||
|
@ -31,6 +31,14 @@
|
|||||||
"target": "Shipment",
|
"target": "Shipment",
|
||||||
"section": "References"
|
"section": "References"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "quote",
|
||||||
|
"label": "Quote Reference",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "SalesQuote",
|
||||||
|
"section": "References",
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "makeAutoStockTransfer",
|
"fieldname": "makeAutoStockTransfer",
|
||||||
"label": "Make Shipment On Submit",
|
"label": "Make Shipment On Submit",
|
||||||
@ -61,6 +69,12 @@
|
|||||||
"target": "SalesInvoice",
|
"target": "SalesInvoice",
|
||||||
"label": "Return Against",
|
"label": "Return Against",
|
||||||
"section": "References"
|
"section": "References"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "isPOS",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": false,
|
||||||
|
"hidden": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"keywordFields": ["name", "party"]
|
"keywordFields": ["name", "party"]
|
||||||
|
46
schemas/app/SalesQuote.json
Normal file
46
schemas/app/SalesQuote.json
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "SalesQuote",
|
||||||
|
"label": "Quote",
|
||||||
|
"extends": "Invoice",
|
||||||
|
"naming": "numberSeries",
|
||||||
|
"showTitle": true,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "numberSeries",
|
||||||
|
"label": "Number Series",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "NumberSeries",
|
||||||
|
"create": true,
|
||||||
|
"required": true,
|
||||||
|
"default": "SQUOT-",
|
||||||
|
"section": "Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "party",
|
||||||
|
"label": "Customer",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Party",
|
||||||
|
"create": true,
|
||||||
|
"required": true,
|
||||||
|
"section": "Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "items",
|
||||||
|
"label": "Items",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"target": "SalesQuoteItem",
|
||||||
|
"required": true,
|
||||||
|
"edit": true,
|
||||||
|
"section": "Items"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"keywordFields": ["name", "party"],
|
||||||
|
"removeFields": [
|
||||||
|
"account",
|
||||||
|
"stockNotTransferred",
|
||||||
|
"backReference",
|
||||||
|
"makeAutoStockTransfer",
|
||||||
|
"returnAgainst",
|
||||||
|
"isReturned"
|
||||||
|
]
|
||||||
|
}
|
5
schemas/app/SalesQuoteItem.json
Normal file
5
schemas/app/SalesQuoteItem.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "SalesQuoteItem",
|
||||||
|
"label": "Sales Quote Item",
|
||||||
|
"extends": "InvoiceItem"
|
||||||
|
}
|
@ -6,12 +6,20 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fieldname": "account",
|
"fieldname": "account",
|
||||||
"label": "Tax Account",
|
"label": "Tax Invoice Account",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"target": "Account",
|
"target": "Account",
|
||||||
"create": true,
|
"create": true,
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "payment_account",
|
||||||
|
"label": "Tax Payment Account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Account",
|
||||||
|
"create": true,
|
||||||
|
"required": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"label": "Rate",
|
"label": "Rate",
|
||||||
@ -20,5 +28,5 @@
|
|||||||
"placeholder": "0%"
|
"placeholder": "0%"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tableFields": ["account", "rate"]
|
"tableFields": ["account", "payment_account", "rate"]
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,14 @@
|
|||||||
"target": "Account",
|
"target": "Account",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "from_account",
|
||||||
|
"label": "Tax Invoice Account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Account",
|
||||||
|
"required": false,
|
||||||
|
"hidden": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "rate",
|
"fieldname": "rate",
|
||||||
"label": "Tax Rate",
|
"label": "Tax Rate",
|
||||||
|
@ -63,6 +63,13 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"default": false,
|
"default": false,
|
||||||
"section": "Features"
|
"section": "Features"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "enablePointOfSale",
|
||||||
|
"label": "Enable Point of Sale",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": false,
|
||||||
|
"section": "Features"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
14
schemas/app/inventory/Point of Sale/CashDenominations.json
Normal file
14
schemas/app/inventory/Point of Sale/CashDenominations.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "CashDenominations",
|
||||||
|
"label": "Cash Denominations",
|
||||||
|
"isAbstract": true,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "denomination",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Denomination",
|
||||||
|
"placeholder": "Denomination",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
42
schemas/app/inventory/Point of Sale/ClosingAmounts.json
Normal file
42
schemas/app/inventory/Point of Sale/ClosingAmounts.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "ClosingAmounts",
|
||||||
|
"label": "Closing Amount",
|
||||||
|
"isChild": true,
|
||||||
|
"extends": "POSShiftAmounts",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "openingAmount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Opening Amount",
|
||||||
|
"placeholder": "Opening Amount",
|
||||||
|
"readOnly": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "closingAmount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Closing Amount",
|
||||||
|
"placeholder": "Closing Amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "expectedAmount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Expected Amount",
|
||||||
|
"placeholder": "Expected Amount",
|
||||||
|
"readOnly": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "differenceAmount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Difference Amount",
|
||||||
|
"placeholder": "Difference Amount",
|
||||||
|
"readOnly": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tableFields": [
|
||||||
|
"paymentMethod",
|
||||||
|
"openingAmount",
|
||||||
|
"closingAmount",
|
||||||
|
"expectedAmount",
|
||||||
|
"differenceAmount"
|
||||||
|
]
|
||||||
|
}
|
17
schemas/app/inventory/Point of Sale/ClosingCash.json
Normal file
17
schemas/app/inventory/Point of Sale/ClosingCash.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "ClosingCash",
|
||||||
|
"label": "Closing Cash In Denominations",
|
||||||
|
"isChild": true,
|
||||||
|
"extends": "CashDenominations",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "count",
|
||||||
|
"label": "Count",
|
||||||
|
"placeholder": "Count",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"default": 0,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tableFields": ["denomination", "count"]
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "DefaultCashDenominations",
|
||||||
|
"label": "Default Cash Denominations",
|
||||||
|
"isChild": true,
|
||||||
|
"extends": "CashDenominations",
|
||||||
|
"tableFields": ["denomination"]
|
||||||
|
}
|
15
schemas/app/inventory/Point of Sale/OpeningAmounts.json
Normal file
15
schemas/app/inventory/Point of Sale/OpeningAmounts.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "OpeningAmounts",
|
||||||
|
"label": "Opening Amount",
|
||||||
|
"isChild": true,
|
||||||
|
"extends": "POSShiftAmounts",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "amount",
|
||||||
|
"label": "Amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"section": "Defaults"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tableFields": ["paymentMethod", "amount"]
|
||||||
|
}
|
17
schemas/app/inventory/Point of Sale/OpeningCash.json
Normal file
17
schemas/app/inventory/Point of Sale/OpeningCash.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "OpeningCash",
|
||||||
|
"label": "Opening Cash In Denominations",
|
||||||
|
"isChild": true,
|
||||||
|
"extends": "CashDenominations",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "count",
|
||||||
|
"label": "Count",
|
||||||
|
"placeholder": "Count",
|
||||||
|
"fieldtype": "Int",
|
||||||
|
"default": 0,
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tableFields": ["denomination", "count"]
|
||||||
|
}
|
36
schemas/app/inventory/Point of Sale/POSSettings.json
Normal file
36
schemas/app/inventory/Point of Sale/POSSettings.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "POSSettings",
|
||||||
|
"label": "POS Settings",
|
||||||
|
"isSingle": true,
|
||||||
|
"isChild": false,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "inventory",
|
||||||
|
"label": "Inventory",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Location",
|
||||||
|
"create": true,
|
||||||
|
"default": "Stores",
|
||||||
|
"section": "Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "cashAccount",
|
||||||
|
"label": "Counter Cash Account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Account",
|
||||||
|
"default": "Cash In Hand",
|
||||||
|
"required": true,
|
||||||
|
"create": true,
|
||||||
|
"section": "Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "writeOffAccount",
|
||||||
|
"label": "Write Off Account",
|
||||||
|
"fieldtype": "Link",
|
||||||
|
"target": "Account",
|
||||||
|
"create": true,
|
||||||
|
"default": "Write Off",
|
||||||
|
"section": "Default"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
43
schemas/app/inventory/Point of Sale/POSShift.json
Normal file
43
schemas/app/inventory/Point of Sale/POSShift.json
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "POSShift",
|
||||||
|
"isSingle": true,
|
||||||
|
"isChild": false,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "isShiftOpen",
|
||||||
|
"label": "Is POS Shift Open",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "openingDate",
|
||||||
|
"label": "Opening Date",
|
||||||
|
"fieldtype": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "closingDate",
|
||||||
|
"label": "Closing Date",
|
||||||
|
"fieldtype": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "openingCash",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"target": "OpeningCash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "closingCash",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"target": "ClosingCash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "openingAmounts",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"target": "OpeningAmounts"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "closingAmounts",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"target": "ClosingAmounts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
25
schemas/app/inventory/Point of Sale/POSShiftAmounts.json
Normal file
25
schemas/app/inventory/Point of Sale/POSShiftAmounts.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "POSShiftAmounts",
|
||||||
|
"label": "POS Shift Amount",
|
||||||
|
"isChild": true,
|
||||||
|
"isAbstract": true,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "paymentMethod",
|
||||||
|
"label": "Payment Method",
|
||||||
|
"placeholder": "Payment Method",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"value": "Cash",
|
||||||
|
"label": "Cash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "Transfer",
|
||||||
|
"label": "Transfer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
21
schemas/regional/ch/AccountingSettings.json
Normal file
21
schemas/regional/ch/AccountingSettings.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "AccountingSettings",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "taxId",
|
||||||
|
"label": "Tax ID",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"placeholder": "CHE-123.456.789",
|
||||||
|
"section": "Default"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quickEditFields": [
|
||||||
|
"fullname",
|
||||||
|
"email",
|
||||||
|
"companyName",
|
||||||
|
"country",
|
||||||
|
"fiscalYearStart",
|
||||||
|
"fiscalYearEnd",
|
||||||
|
"taxId"
|
||||||
|
]
|
||||||
|
}
|
4
schemas/regional/ch/index.ts
Normal file
4
schemas/regional/ch/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { SchemaStub } from '../../types';
|
||||||
|
import AccountingSettings from './AccountingSettings.json';
|
||||||
|
|
||||||
|
export default [AccountingSettings] as SchemaStub[];
|
@ -1,7 +1,11 @@
|
|||||||
import { SchemaStub } from 'schemas/types';
|
import { SchemaStub } from 'schemas/types';
|
||||||
import IndianSchemas from './in';
|
import IndianSchemas from './in';
|
||||||
|
import SwissSchemas from './ch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Regional Schemas are exported by country code.
|
* Regional Schemas are exported by country code.
|
||||||
*/
|
*/
|
||||||
export default { in: IndianSchemas } as Record<string, SchemaStub[]>;
|
export default { in: IndianSchemas, ch: SwissSchemas } as Record<
|
||||||
|
string,
|
||||||
|
SchemaStub[]
|
||||||
|
>;
|
||||||
|
@ -25,6 +25,8 @@ import PurchaseInvoice from './app/PurchaseInvoice.json';
|
|||||||
import PurchaseInvoiceItem from './app/PurchaseInvoiceItem.json';
|
import PurchaseInvoiceItem from './app/PurchaseInvoiceItem.json';
|
||||||
import SalesInvoice from './app/SalesInvoice.json';
|
import SalesInvoice from './app/SalesInvoice.json';
|
||||||
import SalesInvoiceItem from './app/SalesInvoiceItem.json';
|
import SalesInvoiceItem from './app/SalesInvoiceItem.json';
|
||||||
|
import SalesQuote from './app/SalesQuote.json';
|
||||||
|
import SalesQuoteItem from './app/SalesQuoteItem.json';
|
||||||
import SetupWizard from './app/SetupWizard.json';
|
import SetupWizard from './app/SetupWizard.json';
|
||||||
import Tax from './app/Tax.json';
|
import Tax from './app/Tax.json';
|
||||||
import TaxDetail from './app/TaxDetail.json';
|
import TaxDetail from './app/TaxDetail.json';
|
||||||
@ -52,6 +54,15 @@ import base from './meta/base.json';
|
|||||||
import child from './meta/child.json';
|
import child from './meta/child.json';
|
||||||
import submittable from './meta/submittable.json';
|
import submittable from './meta/submittable.json';
|
||||||
import tree from './meta/tree.json';
|
import tree from './meta/tree.json';
|
||||||
|
import CashDenominations from './app/inventory/Point of Sale/CashDenominations.json';
|
||||||
|
import ClosingAmounts from './app/inventory/Point of Sale/ClosingAmounts.json';
|
||||||
|
import ClosingCash from './app/inventory/Point of Sale/ClosingCash.json';
|
||||||
|
import DefaultCashDenominations from './app/inventory/Point of Sale/DefaultCashDenominations.json';
|
||||||
|
import OpeningAmounts from './app/inventory/Point of Sale/OpeningAmounts.json';
|
||||||
|
import OpeningCash from './app/inventory/Point of Sale/OpeningCash.json';
|
||||||
|
import POSSettings from './app/inventory/Point of Sale/POSSettings.json';
|
||||||
|
import POSShift from './app/inventory/Point of Sale/POSShift.json';
|
||||||
|
import POSShiftAmounts from './app/inventory/Point of Sale/POSShiftAmounts.json';
|
||||||
import { Schema, SchemaStub } from './types';
|
import { Schema, SchemaStub } from './types';
|
||||||
|
|
||||||
export const coreSchemas: Schema[] = [
|
export const coreSchemas: Schema[] = [
|
||||||
@ -99,10 +110,12 @@ export const appSchemas: Schema[] | SchemaStub[] = [
|
|||||||
Invoice as Schema,
|
Invoice as Schema,
|
||||||
SalesInvoice as Schema,
|
SalesInvoice as Schema,
|
||||||
PurchaseInvoice as Schema,
|
PurchaseInvoice as Schema,
|
||||||
|
SalesQuote as Schema,
|
||||||
|
|
||||||
InvoiceItem as Schema,
|
InvoiceItem as Schema,
|
||||||
SalesInvoiceItem as SchemaStub,
|
SalesInvoiceItem as SchemaStub,
|
||||||
PurchaseInvoiceItem as SchemaStub,
|
PurchaseInvoiceItem as SchemaStub,
|
||||||
|
SalesQuoteItem as SchemaStub,
|
||||||
|
|
||||||
PriceList as Schema,
|
PriceList as Schema,
|
||||||
PriceListItem as SchemaStub,
|
PriceListItem as SchemaStub,
|
||||||
@ -129,4 +142,14 @@ export const appSchemas: Schema[] | SchemaStub[] = [
|
|||||||
|
|
||||||
CustomForm as Schema,
|
CustomForm as Schema,
|
||||||
CustomField as Schema,
|
CustomField as Schema,
|
||||||
|
|
||||||
|
CashDenominations as Schema,
|
||||||
|
ClosingAmounts as Schema,
|
||||||
|
ClosingCash as Schema,
|
||||||
|
DefaultCashDenominations as Schema,
|
||||||
|
OpeningAmounts as Schema,
|
||||||
|
OpeningCash as Schema,
|
||||||
|
POSSettings as Schema,
|
||||||
|
POSShift as Schema,
|
||||||
|
POSShiftAmounts as Schema,
|
||||||
];
|
];
|
||||||
|
@ -196,7 +196,7 @@ export default {
|
|||||||
this.padding +
|
this.padding +
|
||||||
this.left +
|
this.left +
|
||||||
(i * (this.viewBoxWidth - this.left - 2 * this.padding)) /
|
(i * (this.viewBoxWidth - this.left - 2 * this.padding)) /
|
||||||
(this.count - 1)
|
(this.count - 1 || 1) // The "or" one (1) prevents accidentally dividing by 0
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
z() {
|
z() {
|
||||||
|
@ -193,7 +193,7 @@ export default {
|
|||||||
this.padding +
|
this.padding +
|
||||||
this.left +
|
this.left +
|
||||||
(i * (this.viewBoxWidth - this.left - 2 * this.padding)) /
|
(i * (this.viewBoxWidth - this.left - 2 * this.padding)) /
|
||||||
(this.count - 1)
|
(this.count - 1 || 1) // The "or" one (1) prevents accidentally dividing by 0
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
ys() {
|
ys() {
|
||||||
|
@ -62,6 +62,14 @@ import { defineComponent, PropType } from 'vue';
|
|||||||
import FeatherIcon from '../FeatherIcon.vue';
|
import FeatherIcon from '../FeatherIcon.vue';
|
||||||
import Base from './Base.vue';
|
import Base from './Base.vue';
|
||||||
|
|
||||||
|
const mime_types: Record<string, string> = {
|
||||||
|
png: 'image/png',
|
||||||
|
jpg: 'image/jpeg',
|
||||||
|
jpeg: 'image/jpeg',
|
||||||
|
webp: 'image/webp',
|
||||||
|
svg: 'image/svg+xml',
|
||||||
|
};
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'AttachImage',
|
name: 'AttachImage',
|
||||||
components: { FeatherIcon },
|
components: { FeatherIcon },
|
||||||
@ -99,9 +107,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
const options = {
|
const options = {
|
||||||
title: fyo.t`Select Image`,
|
title: fyo.t`Select Image`,
|
||||||
filters: [
|
filters: [{ name: 'Image', extensions: Object.keys(mime_types) }],
|
||||||
{ name: 'Image', extensions: ['png', 'jpg', 'jpeg', 'webp'] },
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const { name, success, data } = await ipc.selectFile(options);
|
const { name, success, data } = await ipc.selectFile(options);
|
||||||
@ -110,7 +116,7 @@ export default defineComponent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const extension = name.split('.').at(-1);
|
const extension = name.split('.').at(-1);
|
||||||
const type = 'image/' + extension;
|
const type = mime_types[extension];
|
||||||
const dataURL = await getDataURL(type, data);
|
const dataURL = await getDataURL(type, data);
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -71,6 +71,7 @@ export default {
|
|||||||
return this.df.options;
|
return this.df.options;
|
||||||
},
|
},
|
||||||
selectedColorLabel() {
|
selectedColorLabel() {
|
||||||
|
if (!this.colors) return this.value;
|
||||||
const color = this.colors.find((c) => this.value === c.value);
|
const color = this.colors.find((c) => this.value === c.value);
|
||||||
return color ? color.label : this.value;
|
return color ? color.label : this.value;
|
||||||
},
|
},
|
||||||
|
@ -9,6 +9,7 @@ import Inventory from './inventory.vue';
|
|||||||
import Invoice from './invoice.vue';
|
import Invoice from './invoice.vue';
|
||||||
import Item from './item.vue';
|
import Item from './item.vue';
|
||||||
import Mail from './mail.vue';
|
import Mail from './mail.vue';
|
||||||
|
import POS from './pos.vue';
|
||||||
import OpeningAc from './opening-ac.vue';
|
import OpeningAc from './opening-ac.vue';
|
||||||
import Percentage from './percentage.vue';
|
import Percentage from './percentage.vue';
|
||||||
import Property from './property.vue';
|
import Property from './property.vue';
|
||||||
@ -36,6 +37,7 @@ export default {
|
|||||||
'invoice': Invoice,
|
'invoice': Invoice,
|
||||||
'item': Item,
|
'item': Item,
|
||||||
'mail': Mail,
|
'mail': Mail,
|
||||||
|
'pos': POS,
|
||||||
'opening-ac': OpeningAc,
|
'opening-ac': OpeningAc,
|
||||||
'percentage': Percentage,
|
'percentage': Percentage,
|
||||||
'property': Property,
|
'property': Property,
|
||||||
|
15
src/components/Icons/18/pos.vue
Normal file
15
src/components/Icons/18/pos.vue
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<template>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
|
<path fill="none" d="M0 0h24v24H0z"></path>
|
||||||
|
<path
|
||||||
|
:fill="darkColor"
|
||||||
|
d="M21 13V20C21 20.5523 20.5523 21 20 21H4C3.44772 21 3 20.5523 3 20V13H2V11L3 6H21L22 11V13H21ZM5 13V19H19V13H5ZM6 14H14V17H6V14ZM3 3H21V5H3V3Z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Base from '../base.vue';
|
||||||
|
export default {
|
||||||
|
extends: Base,
|
||||||
|
};
|
||||||
|
</script>
|
116
src/components/POS/FloatingLabelCurrencyInput.vue
Normal file
116
src/components/POS/FloatingLabelCurrencyInput.vue
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
:type="inputType"
|
||||||
|
:class="[inputClasses, size === 'large' ? 'text-lg' : 'text-sm']"
|
||||||
|
:value="round(value)"
|
||||||
|
:max="isNumeric(df) ? df.maxvalue : undefined"
|
||||||
|
:min="isNumeric(df) ? df.minvalue : undefined"
|
||||||
|
:readonly="isReadOnly"
|
||||||
|
:tabindex="isReadOnly ? '-1' : '0'"
|
||||||
|
@blur="onBlur"
|
||||||
|
class="
|
||||||
|
block
|
||||||
|
px-2.5
|
||||||
|
pb-2.5
|
||||||
|
pt-4
|
||||||
|
w-full
|
||||||
|
font-medium
|
||||||
|
text-gray-900
|
||||||
|
bg-gray-25
|
||||||
|
rounded-lg
|
||||||
|
border border-gray-200
|
||||||
|
appearance-none
|
||||||
|
focus:outline-none focus:ring-0
|
||||||
|
peer
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
for="floating_outlined"
|
||||||
|
:class="size === 'large' ? 'text-xl' : 'text-md'"
|
||||||
|
class="
|
||||||
|
absolute
|
||||||
|
font-medium
|
||||||
|
text-gray-500
|
||||||
|
duration-300
|
||||||
|
transform
|
||||||
|
-translate-y-4
|
||||||
|
scale-75
|
||||||
|
top-8
|
||||||
|
z-10
|
||||||
|
origin-[0]
|
||||||
|
bg-white2
|
||||||
|
px-2
|
||||||
|
peer-focus:px-2 peer-focus:text-blue-600
|
||||||
|
peer-placeholder-shown:scale-100
|
||||||
|
peer-placeholder-shown:-translate-y-1/2
|
||||||
|
peer-placeholder-shown:top-1/2
|
||||||
|
peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4
|
||||||
|
left-1
|
||||||
|
"
|
||||||
|
>{{ currency ? fyo.currencySymbols[currency] : undefined }}</label
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
for="floating_outlined"
|
||||||
|
:class="size === 'large' ? 'text-xl' : 'text-md'"
|
||||||
|
class="
|
||||||
|
absolute
|
||||||
|
font-medium
|
||||||
|
text-gray-500
|
||||||
|
duration-300
|
||||||
|
transform
|
||||||
|
-translate-y-4
|
||||||
|
scale-75
|
||||||
|
top-1
|
||||||
|
z-10
|
||||||
|
origin-[0]
|
||||||
|
bg-white2
|
||||||
|
px-2
|
||||||
|
peer-focus:px-2 peer-focus:text-blue-600
|
||||||
|
peer-placeholder-shown:scale-100
|
||||||
|
peer-placeholder-shown:-translate-y-1/2
|
||||||
|
peer-placeholder-shown:top-1/2
|
||||||
|
peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4
|
||||||
|
left-1
|
||||||
|
"
|
||||||
|
>{{ df.label }}</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import FloatingLabelInputBase from './FloatingLabelInputBase.vue';
|
||||||
|
import { safeParsePesa } from 'utils/index';
|
||||||
|
import { isPesa } from 'fyo/utils';
|
||||||
|
import { fyo } from 'src/initFyo';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FloatingLabelCurrencyInput',
|
||||||
|
extends: FloatingLabelInputBase,
|
||||||
|
computed: {
|
||||||
|
currency(): string | undefined {
|
||||||
|
if (this.value) {
|
||||||
|
return (this.value as Money).getCurrency();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
round(v: unknown) {
|
||||||
|
if (!isPesa(v)) {
|
||||||
|
v = this.parse(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPesa(v)) {
|
||||||
|
return v.round();
|
||||||
|
}
|
||||||
|
|
||||||
|
return fyo.pesa(0).round();
|
||||||
|
},
|
||||||
|
parse(value: unknown): Money {
|
||||||
|
return safeParsePesa(value, this.fyo);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
14
src/components/POS/FloatingLabelFloatInput.vue
Normal file
14
src/components/POS/FloatingLabelFloatInput.vue
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import FloatingLabelInputBase from './FloatingLabelInputBase.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FloatingLabelFloatInput',
|
||||||
|
extends: FloatingLabelInputBase,
|
||||||
|
computed: {
|
||||||
|
inputType() {
|
||||||
|
return 'number';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
63
src/components/POS/FloatingLabelInputBase.vue
Normal file
63
src/components/POS/FloatingLabelInputBase.vue
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<div class="relative">
|
||||||
|
<input
|
||||||
|
:type="inputType"
|
||||||
|
:class="[inputClasses, size === 'large' ? 'text-lg' : 'text-sm']"
|
||||||
|
:value="value"
|
||||||
|
:max="isNumeric(df) ? df.maxvalue : undefined"
|
||||||
|
:min="isNumeric(df) ? df.minvalue : undefined"
|
||||||
|
:readonly="isReadOnly"
|
||||||
|
:tabindex="isReadOnly ? '-1' : '0'"
|
||||||
|
@blur="onBlur"
|
||||||
|
class="
|
||||||
|
block
|
||||||
|
px-2.5
|
||||||
|
pb-2.5
|
||||||
|
pt-4
|
||||||
|
w-full
|
||||||
|
font-medium
|
||||||
|
text-gray-900
|
||||||
|
bg-gray-25
|
||||||
|
rounded-lg
|
||||||
|
border border-gray-200
|
||||||
|
appearance-none
|
||||||
|
focus:outline-none focus:ring-0
|
||||||
|
peer
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
for="floating_outlined"
|
||||||
|
:class="size === 'large' ? 'text-xl' : 'text-md'"
|
||||||
|
class="
|
||||||
|
absolute
|
||||||
|
font-medium
|
||||||
|
text-gray-500
|
||||||
|
duration-300
|
||||||
|
transform
|
||||||
|
-translate-y-4
|
||||||
|
scale-75
|
||||||
|
top-1
|
||||||
|
z-10
|
||||||
|
origin-[0]
|
||||||
|
bg-white2
|
||||||
|
px-2
|
||||||
|
peer-focus:px-2 peer-focus:text-blue-600 peer-focus:dark:text-blue-500
|
||||||
|
peer-placeholder-shown:scale-100
|
||||||
|
peer-placeholder-shown:-translate-y-1/2
|
||||||
|
peer-placeholder-shown:top-1/2
|
||||||
|
peer-focus:top-2 peer-focus:scale-75 peer-focus:-translate-y-4
|
||||||
|
left-1
|
||||||
|
"
|
||||||
|
>{{ df.label }}</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import Base from '../Controls/Base.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FloatingLabelInputBase',
|
||||||
|
extends: Base,
|
||||||
|
});
|
||||||
|
</script>
|
166
src/components/POS/ItemsTable.vue
Normal file
166
src/components/POS/ItemsTable.vue
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<Row
|
||||||
|
:ratio="ratio"
|
||||||
|
class="border flex items-center mt-4 px-2 rounded-t-md text-gray-600 w-full"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="df in tableFields"
|
||||||
|
:key="df.fieldname"
|
||||||
|
class="flex items-center px-2 py-2 text-lg"
|
||||||
|
:class="{
|
||||||
|
'ms-auto': isNumeric(df as Field),
|
||||||
|
}"
|
||||||
|
:style="{
|
||||||
|
height: ``,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ df.label }}
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<div class="overflow-y-auto" style="height: 72.5vh">
|
||||||
|
<Row
|
||||||
|
v-if="items"
|
||||||
|
v-for="row in items"
|
||||||
|
:ratio="ratio"
|
||||||
|
:border="true"
|
||||||
|
class="
|
||||||
|
border-b border-l border-r
|
||||||
|
flex
|
||||||
|
group
|
||||||
|
h-row-mid
|
||||||
|
hover:bg-gray-25
|
||||||
|
items-center
|
||||||
|
justify-center
|
||||||
|
px-2
|
||||||
|
w-full
|
||||||
|
"
|
||||||
|
@click="handleChange(row as POSItem)"
|
||||||
|
>
|
||||||
|
<FormControl
|
||||||
|
v-for="df in tableFields"
|
||||||
|
:key="df.fieldname"
|
||||||
|
size="large"
|
||||||
|
class=""
|
||||||
|
:df="df"
|
||||||
|
:value="row[df.fieldname]"
|
||||||
|
:readOnly="true"
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import FormControl from '../Controls/FormControl.vue';
|
||||||
|
import Row from 'src/components/Row.vue';
|
||||||
|
import { isNumeric } from 'src/utils';
|
||||||
|
import { inject } from 'vue';
|
||||||
|
import { fyo } from 'src/initFyo';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { Field } from 'schemas/types';
|
||||||
|
import { ItemQtyMap } from './types';
|
||||||
|
import { Item } from 'models/baseModels/Item/Item';
|
||||||
|
import { POSItem } from './types';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ItemsTable',
|
||||||
|
components: { FormControl, Row },
|
||||||
|
emits: ['addItem', 'updateValues'],
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
itemQtyMap: inject('itemQtyMap') as ItemQtyMap,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
items: [] as POSItem[],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
ratio() {
|
||||||
|
return [1, 1, 1, 0.7];
|
||||||
|
},
|
||||||
|
tableFields() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldname: 'name',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
label: 'Item',
|
||||||
|
placeholder: 'Item',
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'rate',
|
||||||
|
label: 'Rate',
|
||||||
|
placeholder: 'Rate',
|
||||||
|
fieldtype: 'Currency',
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'availableQty',
|
||||||
|
label: 'Available Qty',
|
||||||
|
placeholder: 'Available Qty',
|
||||||
|
fieldtype: 'Float',
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'unit',
|
||||||
|
label: 'Unit',
|
||||||
|
placeholder: 'Unit',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
target: 'UOM',
|
||||||
|
readOnly: true,
|
||||||
|
},
|
||||||
|
] as Field[];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
itemQtyMap: {
|
||||||
|
async handler() {
|
||||||
|
this.setItems();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async activated() {
|
||||||
|
await this.setItems();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async setItems() {
|
||||||
|
const items = (await fyo.db.getAll(ModelNameEnum.Item, {
|
||||||
|
fields: [],
|
||||||
|
filters: { trackItem: true },
|
||||||
|
})) as Item[];
|
||||||
|
|
||||||
|
this.items = [] as POSItem[];
|
||||||
|
for (const item of items) {
|
||||||
|
let availableQty = 0;
|
||||||
|
|
||||||
|
if (!!this.itemQtyMap[item.name as string]) {
|
||||||
|
availableQty = this.itemQtyMap[item.name as string].availableQty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.items.push({
|
||||||
|
availableQty,
|
||||||
|
name: item.name,
|
||||||
|
rate: item.rate as Money,
|
||||||
|
unit: item.unit as string,
|
||||||
|
hasBatch: !!item.hasBatch,
|
||||||
|
hasSerialNumber: !!item.hasSerialNumber,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleChange(value: POSItem) {
|
||||||
|
this.$emit('addItem', value);
|
||||||
|
this.$emit('updateValues');
|
||||||
|
},
|
||||||
|
isNumeric,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
354
src/components/POS/SelectedItemRow.vue
Normal file
354
src/components/POS/SelectedItemRow.vue
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
<template>
|
||||||
|
<feather-icon
|
||||||
|
:name="isExapanded ? 'chevron-up' : 'chevron-down'"
|
||||||
|
class="w-4 h-4 inline-flex"
|
||||||
|
@click="isExapanded = !isExapanded"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
:df="{
|
||||||
|
fieldname: 'item',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
label: 'item',
|
||||||
|
}"
|
||||||
|
size="small"
|
||||||
|
:border="false"
|
||||||
|
:value="row.item"
|
||||||
|
:read-only="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Int
|
||||||
|
:df="{
|
||||||
|
fieldname: 'quantity',
|
||||||
|
fieldtype: 'Int',
|
||||||
|
label: 'Quantity',
|
||||||
|
}"
|
||||||
|
size="small"
|
||||||
|
:border="false"
|
||||||
|
:value="row.quantity"
|
||||||
|
:read-only="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
:df="{
|
||||||
|
fieldname: 'unit',
|
||||||
|
fieldtype: 'Data',
|
||||||
|
label: 'Unit',
|
||||||
|
}"
|
||||||
|
size="small"
|
||||||
|
:border="false"
|
||||||
|
:value="row.unit"
|
||||||
|
:read-only="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Currency
|
||||||
|
:df="{
|
||||||
|
fieldtype: 'Currency',
|
||||||
|
fieldname: 'rate',
|
||||||
|
label: 'rate',
|
||||||
|
}"
|
||||||
|
size="small"
|
||||||
|
:border="false"
|
||||||
|
:value="row.rate"
|
||||||
|
:read-only="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Currency
|
||||||
|
:df="{
|
||||||
|
fieldtype: 'Currency',
|
||||||
|
fieldname: 'amount',
|
||||||
|
label: 'Amount',
|
||||||
|
}"
|
||||||
|
size="small"
|
||||||
|
:border="false"
|
||||||
|
:value="row.amount"
|
||||||
|
:read-only="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="px-4">
|
||||||
|
<feather-icon
|
||||||
|
name="trash"
|
||||||
|
class="w-4 text-xl text-red-500"
|
||||||
|
@click="$emit('removeItem', row.idx)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div></div>
|
||||||
|
|
||||||
|
<template v-if="isExapanded">
|
||||||
|
<div class="px-4 pt-6 col-span-1">
|
||||||
|
<Float
|
||||||
|
:df="{
|
||||||
|
fieldname: 'quantity',
|
||||||
|
fieldtype: 'Float',
|
||||||
|
label: 'Quantity',
|
||||||
|
}"
|
||||||
|
size="medium"
|
||||||
|
:min="0"
|
||||||
|
:border="true"
|
||||||
|
:show-label="true"
|
||||||
|
:value="row.quantity"
|
||||||
|
@change="(value:number) => (row.quantity = value)"
|
||||||
|
:read-only="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 pt-6 col-span-2 flex">
|
||||||
|
<Link
|
||||||
|
v-if="isUOMConversionEnabled"
|
||||||
|
:df="{
|
||||||
|
fieldname: 'transferUnit',
|
||||||
|
fieldtype: 'Link',
|
||||||
|
target: 'UOM',
|
||||||
|
label: t`Transfer Unit`,
|
||||||
|
}"
|
||||||
|
class="flex-1"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:value="row.transferUnit"
|
||||||
|
@change="(value:string) => setTransferUnit((row.transferUnit = value))"
|
||||||
|
/>
|
||||||
|
<feather-icon
|
||||||
|
v-if="isUOMConversionEnabled"
|
||||||
|
name="refresh-ccw"
|
||||||
|
class="w-3.5 ml-2 mt-4 text-blue-500"
|
||||||
|
@click="row.transferUnit = row.unit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 pt-6 col-span-2">
|
||||||
|
<Int
|
||||||
|
v-if="isUOMConversionEnabled"
|
||||||
|
:df="{
|
||||||
|
fieldtype: 'Int',
|
||||||
|
fieldname: 'transferQuantity',
|
||||||
|
label: 'Transfer Quantity',
|
||||||
|
}"
|
||||||
|
size="medium"
|
||||||
|
:border="true"
|
||||||
|
:show-label="true"
|
||||||
|
:value="row.transferQuantity"
|
||||||
|
@change="(value:number) => setTransferQty((row.transferQuantity = value))"
|
||||||
|
:read-only="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
|
||||||
|
<div class="px-4 pt-6 flex">
|
||||||
|
<Currency
|
||||||
|
:df="{
|
||||||
|
fieldtype: 'Currency',
|
||||||
|
fieldname: 'rate',
|
||||||
|
label: 'Rate',
|
||||||
|
}"
|
||||||
|
size="medium"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:value="row.rate"
|
||||||
|
:read-only="false"
|
||||||
|
@change="(value:Money) => (row.rate = value)"
|
||||||
|
/>
|
||||||
|
<feather-icon
|
||||||
|
name="refresh-ccw"
|
||||||
|
class="w-3.5 ml-2 mt-5 text-blue-500 flex-none"
|
||||||
|
@click="row.rate= (defaultRate as Money)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="px-6 pt-6 col-span-2">
|
||||||
|
<Currency
|
||||||
|
v-if="isDiscountingEnabled"
|
||||||
|
:df="{
|
||||||
|
fieldtype: 'Currency',
|
||||||
|
fieldname: 'discountAmount',
|
||||||
|
label: 'Discount Amount',
|
||||||
|
}"
|
||||||
|
class="col-span-2"
|
||||||
|
size="medium"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:value="row.itemDiscountAmount"
|
||||||
|
:read-only="row.itemDiscountPercent as number > 0"
|
||||||
|
@change="(value:number) => setItemDiscount('amount', value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="px-4 pt-6 col-span-2">
|
||||||
|
<Float
|
||||||
|
v-if="isDiscountingEnabled"
|
||||||
|
:df="{
|
||||||
|
fieldtype: 'Float',
|
||||||
|
fieldname: 'itemDiscountPercent',
|
||||||
|
label: 'Discount Percent',
|
||||||
|
}"
|
||||||
|
size="medium"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:value="row.itemDiscountPercent"
|
||||||
|
:read-only="!row.itemDiscountAmount?.isZero()"
|
||||||
|
@change="(value:number) => setItemDiscount('percent', value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=""></div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="row.links?.item && row.links?.item.hasBatch"
|
||||||
|
class="pl-6 px-4 pt-6 col-span-2"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
:df="{
|
||||||
|
fieldname: 'batch',
|
||||||
|
fieldtype: 'Link',
|
||||||
|
target: 'Batch',
|
||||||
|
label: t`Batch`,
|
||||||
|
}"
|
||||||
|
value=""
|
||||||
|
:border="true"
|
||||||
|
:show-label="true"
|
||||||
|
:read-only="false"
|
||||||
|
@change="(value:string) => setBatch(value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="row.links?.item && row.links?.item.hasBatch"
|
||||||
|
class="px-2 pt-6 col-span-2"
|
||||||
|
>
|
||||||
|
<Float
|
||||||
|
:df="{
|
||||||
|
fieldname: 'availableQtyInBatch',
|
||||||
|
fieldtype: 'Float',
|
||||||
|
label: t`Qty in Batch`,
|
||||||
|
}"
|
||||||
|
size="medium"
|
||||||
|
:min="0"
|
||||||
|
:value="availableQtyInBatch"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:read-only="true"
|
||||||
|
:text-right="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="hasSerialNumber" class="px-2 pt-8 col-span-2">
|
||||||
|
<Text
|
||||||
|
:df="{
|
||||||
|
label: t`Serial Number`,
|
||||||
|
fieldtype: 'Text',
|
||||||
|
fieldname: 'serialNumber',
|
||||||
|
}"
|
||||||
|
:value="row.serialNumber"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:required="hasSerialNumber"
|
||||||
|
@change="(value:string)=> setSerialNumber(value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Currency from '../Controls/Currency.vue';
|
||||||
|
import Data from '../Controls/Data.vue';
|
||||||
|
import Float from '../Controls/Float.vue';
|
||||||
|
import Int from '../Controls/Int.vue';
|
||||||
|
import Link from '../Controls/Link.vue';
|
||||||
|
import Text from '../Controls/Text.vue';
|
||||||
|
import { inject } from 'vue';
|
||||||
|
import { fyo } from 'src/initFyo';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
import { DiscountType } from './types';
|
||||||
|
import { t } from 'fyo';
|
||||||
|
import { validateSerialNumberCount } from 'src/utils/pos';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'SelectedItemRow',
|
||||||
|
components: { Currency, Data, Float, Int, Link, Text },
|
||||||
|
props: {
|
||||||
|
row: { type: SalesInvoiceItem, required: true },
|
||||||
|
},
|
||||||
|
emits: ['removeItem', 'runSinvFormulas', 'setItemSerialNumbers'],
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
isDiscountingEnabled: inject('isDiscountingEnabled') as boolean,
|
||||||
|
itemSerialNumbers: inject('itemSerialNumbers') as {
|
||||||
|
[item: string]: string;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isExapanded: false,
|
||||||
|
batches: [] as string[],
|
||||||
|
availableQtyInBatch: 0,
|
||||||
|
|
||||||
|
defaultRate: this.row.rate as Money,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isUOMConversionEnabled(): boolean {
|
||||||
|
return !!fyo.singles.InventorySettings?.enableUomConversions;
|
||||||
|
},
|
||||||
|
hasSerialNumber(): boolean {
|
||||||
|
return !!(this.row.links?.item && this.row.links?.item.hasSerialNumber);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async getAvailableQtyInBatch(): Promise<number> {
|
||||||
|
if (!this.row.batch) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
(await fyo.db.getStockQuantity(
|
||||||
|
this.row.item as string,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
this.row.batch
|
||||||
|
)) ?? 0
|
||||||
|
);
|
||||||
|
},
|
||||||
|
async setBatch(batch: string) {
|
||||||
|
this.row.batch = batch;
|
||||||
|
this.availableQtyInBatch = await this.getAvailableQtyInBatch();
|
||||||
|
},
|
||||||
|
setSerialNumber(serialNumber: string) {
|
||||||
|
if (!serialNumber) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.itemSerialNumbers[this.row.item as string] = serialNumber;
|
||||||
|
|
||||||
|
validateSerialNumberCount(
|
||||||
|
serialNumber,
|
||||||
|
this.row.quantity ?? 0,
|
||||||
|
this.row.item!
|
||||||
|
);
|
||||||
|
},
|
||||||
|
setItemDiscount(type: DiscountType, value: Money | number) {
|
||||||
|
if (type === 'percent') {
|
||||||
|
this.row.setItemDiscountAmount = false;
|
||||||
|
this.row.itemDiscountPercent = value as number;
|
||||||
|
this.$emit('runSinvFormulas');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.row.setItemDiscountAmount = true;
|
||||||
|
this.row.itemDiscountAmount = value as Money;
|
||||||
|
this.$emit('runSinvFormulas');
|
||||||
|
},
|
||||||
|
setTransferUnit(unit: string) {
|
||||||
|
this.row.setTransferUnit = unit;
|
||||||
|
this.row._applyFormula('transferUnit');
|
||||||
|
},
|
||||||
|
setTransferQty(quantity: number) {
|
||||||
|
this.row.transferQuantity = quantity;
|
||||||
|
this.row._applyFormula('transferQuantity');
|
||||||
|
this.$emit('runSinvFormulas');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
150
src/components/POS/SelectedItemTable.vue
Normal file
150
src/components/POS/SelectedItemTable.vue
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
<template>
|
||||||
|
<Row
|
||||||
|
:ratio="ratio"
|
||||||
|
class="border rounded-t px-2 text-gray-600 w-full flex items-center mt-4"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="tableFields"
|
||||||
|
v-for="df in tableFields"
|
||||||
|
:key="df.fieldname"
|
||||||
|
class="items-center text-lg flex px-2 py-2"
|
||||||
|
:class="{
|
||||||
|
'ms-auto': isNumeric(df as Field),
|
||||||
|
}"
|
||||||
|
:style="{
|
||||||
|
height: ``,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ df.label }}
|
||||||
|
</div>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
<div class="overflow-y-auto" style="height: 50vh">
|
||||||
|
<Row
|
||||||
|
v-for="row in sinvDoc.items"
|
||||||
|
:ratio="ratio"
|
||||||
|
class="
|
||||||
|
border
|
||||||
|
w-full
|
||||||
|
px-2
|
||||||
|
py-2
|
||||||
|
group
|
||||||
|
flex
|
||||||
|
items-center
|
||||||
|
justify-center
|
||||||
|
hover:bg-gray-25
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<SelectedItemRow
|
||||||
|
:row="(row as SalesInvoiceItem)"
|
||||||
|
@remove-item="removeItem"
|
||||||
|
@run-sinv-formulas="runSinvFormulas"
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import FormContainer from '../FormContainer.vue';
|
||||||
|
import FormControl from '../Controls/FormControl.vue';
|
||||||
|
import Link from '../Controls/Link.vue';
|
||||||
|
import Row from '../Row.vue';
|
||||||
|
import RowEditForm from 'src/pages/CommonForm/RowEditForm.vue';
|
||||||
|
import SelectedItemRow from './SelectedItemRow.vue';
|
||||||
|
import { isNumeric } from 'src/utils';
|
||||||
|
import { inject } from 'vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||||
|
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||||
|
import { Field } from 'schemas/types';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'SelectedItemTable',
|
||||||
|
components: {
|
||||||
|
FormContainer,
|
||||||
|
FormControl,
|
||||||
|
Link,
|
||||||
|
Row,
|
||||||
|
RowEditForm,
|
||||||
|
SelectedItemRow,
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
sinvDoc: inject('sinvDoc') as SalesInvoice,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isExapanded: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
ratio() {
|
||||||
|
return [0.1, 1, 0.8, 0.8, 0.8, 0.8, 0.2];
|
||||||
|
},
|
||||||
|
tableFields() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
fieldname: 'toggler',
|
||||||
|
fieldtype: 'Link',
|
||||||
|
label: ' ',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'item',
|
||||||
|
fieldtype: 'Link',
|
||||||
|
label: 'Item',
|
||||||
|
placeholder: 'Item',
|
||||||
|
required: true,
|
||||||
|
schemaName: 'Item',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'quantity',
|
||||||
|
label: 'Quantity',
|
||||||
|
placeholder: 'Quantity',
|
||||||
|
fieldtype: 'Int',
|
||||||
|
required: true,
|
||||||
|
schemaName: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'unit',
|
||||||
|
label: 'Stock Unit',
|
||||||
|
placeholder: 'Unit',
|
||||||
|
fieldtype: 'Link',
|
||||||
|
required: true,
|
||||||
|
schemaName: 'UOM',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'rate',
|
||||||
|
label: 'Rate',
|
||||||
|
placeholder: 'Rate',
|
||||||
|
fieldtype: 'Currency',
|
||||||
|
required: true,
|
||||||
|
schemaName: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'amount',
|
||||||
|
label: 'Amount',
|
||||||
|
placeholder: 'Amount',
|
||||||
|
fieldtype: 'Currency',
|
||||||
|
required: true,
|
||||||
|
schemaName: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: 'removeItem',
|
||||||
|
fieldtype: 'Link',
|
||||||
|
label: ' ',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
removeItem(idx: number) {
|
||||||
|
this.sinvDoc.remove('items', idx);
|
||||||
|
},
|
||||||
|
async runSinvFormulas() {
|
||||||
|
await this.sinvDoc.runFormulas();
|
||||||
|
},
|
||||||
|
isNumeric,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
20
src/components/POS/types.ts
Normal file
20
src/components/POS/types.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { Money } from "pesa";
|
||||||
|
|
||||||
|
export type ItemQtyMap = {
|
||||||
|
[item: string]: { availableQty: number;[batch: string]: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ItemSerialNumbers = { [item: string]: string };
|
||||||
|
|
||||||
|
export type DiscountType = "percent" | "amount";
|
||||||
|
|
||||||
|
export type ModalName = 'ShiftOpen' | 'ShiftClose' | 'Payment'
|
||||||
|
|
||||||
|
export interface POSItem {
|
||||||
|
name: string,
|
||||||
|
rate: Money,
|
||||||
|
availableQty: number,
|
||||||
|
unit: string,
|
||||||
|
hasBatch: boolean,
|
||||||
|
hasSerialNumber: boolean,
|
||||||
|
}
|
@ -150,6 +150,7 @@
|
|||||||
v-if="showDevMode"
|
v-if="showDevMode"
|
||||||
class="text-xs text-gray-500 select-none cursor-pointer"
|
class="text-xs text-gray-500 select-none cursor-pointer"
|
||||||
@click="showDevMode = false"
|
@click="showDevMode = false"
|
||||||
|
title="Open dev tools with Ctrl+Shift+I"
|
||||||
>
|
>
|
||||||
dev mode
|
dev mode
|
||||||
</p>
|
</p>
|
||||||
|
209
src/pages/POS/ClosePOSShiftModal.vue
Normal file
209
src/pages/POS/ClosePOSShiftModal.vue
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
<template>
|
||||||
|
<Modal :open-modal="openModal" class="w-3/6 p-4">
|
||||||
|
<h1 class="text-xl font-semibold text-center pb-4">Close POS Shift</h1>
|
||||||
|
|
||||||
|
<h2 class="mt-4 mb-2 text-lg font-medium">Closing Cash</h2>
|
||||||
|
<Table
|
||||||
|
v-if="isValuesSeeded"
|
||||||
|
class="text-base"
|
||||||
|
:df="getField('closingCash')"
|
||||||
|
:show-header="true"
|
||||||
|
:border="true"
|
||||||
|
:value="posShiftDoc?.closingCash ?? []"
|
||||||
|
:read-only="false"
|
||||||
|
@row-change="handleChange"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h2 class="mt-6 mb-2 text-lg font-medium">Closing Amounts</h2>
|
||||||
|
<Table
|
||||||
|
v-if="isValuesSeeded"
|
||||||
|
class="text-base"
|
||||||
|
:df="getField('closingAmounts')"
|
||||||
|
:show-header="true"
|
||||||
|
:border="true"
|
||||||
|
:value="posShiftDoc?.closingAmounts"
|
||||||
|
:read-only="true"
|
||||||
|
@row-change="handleChange"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="mt-4 grid grid-cols-2 gap-4 flex items-end">
|
||||||
|
<Button
|
||||||
|
class="w-full py-5 bg-red-500"
|
||||||
|
@click="$emit('toggleModal', 'ShiftClose', false)"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<p class="uppercase text-lg text-white font-semibold">
|
||||||
|
{{ t`Cancel` }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button class="w-full py-5 bg-green-500" @click="handleSubmit">
|
||||||
|
<slot>
|
||||||
|
<p class="uppercase text-lg text-white font-semibold">
|
||||||
|
{{ t`Submit` }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Button from 'src/components/Button.vue';
|
||||||
|
import Modal from 'src/components/Modal.vue';
|
||||||
|
import Table from 'src/components/Controls/Table.vue';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
import { OpeningAmounts } from 'models/inventory/Point of Sale/OpeningAmounts';
|
||||||
|
import { POSShift } from 'models/inventory/Point of Sale/POSShift';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { fyo } from 'src/initFyo';
|
||||||
|
import { showToast } from 'src/utils/interactive';
|
||||||
|
import { t } from 'fyo';
|
||||||
|
import {
|
||||||
|
validateClosingAmounts,
|
||||||
|
transferPOSCashAndWriteOff,
|
||||||
|
} from 'src/utils/pos';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ClosePOSShiftModal',
|
||||||
|
components: { Button, Modal, Table },
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
doc: computed(() => this.posShiftDoc),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
openModal: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['toggleModal'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isValuesSeeded: false,
|
||||||
|
|
||||||
|
posShiftDoc: undefined as POSShift | undefined,
|
||||||
|
transactedAmount: {} as Record<string, Money> | undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
openModal: {
|
||||||
|
async handler() {
|
||||||
|
await this.setTransactedAmount();
|
||||||
|
await this.seedClosingAmounts();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async activated() {
|
||||||
|
this.posShiftDoc = fyo.singles[ModelNameEnum.POSShift];
|
||||||
|
await this.seedValues();
|
||||||
|
await this.setTransactedAmount();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async setTransactedAmount() {
|
||||||
|
if (!fyo.singles.POSShift?.openingDate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const fromDate = fyo.singles.POSShift?.openingDate;
|
||||||
|
this.transactedAmount = await fyo.db.getPOSTransactedAmount(
|
||||||
|
fromDate,
|
||||||
|
new Date(),
|
||||||
|
fyo.singles.POSShift.closingDate as Date
|
||||||
|
);
|
||||||
|
},
|
||||||
|
seedClosingCash() {
|
||||||
|
if (!this.posShiftDoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.posShiftDoc.closingCash = [];
|
||||||
|
|
||||||
|
this.posShiftDoc?.openingCash?.map(async (row) => {
|
||||||
|
await this.posShiftDoc?.append('closingCash', {
|
||||||
|
count: row.count,
|
||||||
|
denomination: row.denomination as Money,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async seedClosingAmounts() {
|
||||||
|
if (!this.posShiftDoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.posShiftDoc.closingAmounts = [];
|
||||||
|
await this.posShiftDoc.sync();
|
||||||
|
|
||||||
|
const openingAmounts = this.posShiftDoc
|
||||||
|
.openingAmounts as OpeningAmounts[];
|
||||||
|
|
||||||
|
for (const row of openingAmounts) {
|
||||||
|
if (!row.paymentMethod) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let expectedAmount = fyo.pesa(0);
|
||||||
|
|
||||||
|
if (row.paymentMethod === 'Cash') {
|
||||||
|
expectedAmount = expectedAmount.add(
|
||||||
|
this.posShiftDoc.openingCashAmount as Money
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.paymentMethod === 'Transfer') {
|
||||||
|
expectedAmount = expectedAmount.add(
|
||||||
|
this.posShiftDoc.openingTransferAmount as Money
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.transactedAmount) {
|
||||||
|
expectedAmount = expectedAmount.add(
|
||||||
|
this.transactedAmount[row.paymentMethod]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.posShiftDoc.append('closingAmounts', {
|
||||||
|
paymentMethod: row.paymentMethod,
|
||||||
|
openingAmount: row.amount,
|
||||||
|
closingAmount: fyo.pesa(0),
|
||||||
|
expectedAmount: expectedAmount,
|
||||||
|
differenceAmount: fyo.pesa(0),
|
||||||
|
});
|
||||||
|
await this.posShiftDoc.sync();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async seedValues() {
|
||||||
|
this.isValuesSeeded = false;
|
||||||
|
this.seedClosingCash();
|
||||||
|
await this.seedClosingAmounts();
|
||||||
|
this.isValuesSeeded = true;
|
||||||
|
},
|
||||||
|
getField(fieldname: string) {
|
||||||
|
return fyo.getField(ModelNameEnum.POSShift, fieldname);
|
||||||
|
},
|
||||||
|
async handleChange() {
|
||||||
|
await this.posShiftDoc?.sync();
|
||||||
|
},
|
||||||
|
async handleSubmit() {
|
||||||
|
try {
|
||||||
|
validateClosingAmounts(this.posShiftDoc as POSShift);
|
||||||
|
await this.posShiftDoc?.set('isShiftOpen', false);
|
||||||
|
await this.posShiftDoc?.set('closingDate', new Date());
|
||||||
|
await this.posShiftDoc?.sync();
|
||||||
|
await transferPOSCashAndWriteOff(fyo, this.posShiftDoc as POSShift);
|
||||||
|
|
||||||
|
this.$emit('toggleModal', 'ShiftClose');
|
||||||
|
} catch (error) {
|
||||||
|
return showToast({
|
||||||
|
type: 'error',
|
||||||
|
message: t`${error as string}`,
|
||||||
|
duration: 'short',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
223
src/pages/POS/OpenPOSShiftModal.vue
Normal file
223
src/pages/POS/OpenPOSShiftModal.vue
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
<template>
|
||||||
|
<Modal class="w-3/6 p-4">
|
||||||
|
<h1 class="text-xl font-semibold text-center pb-4">Open POS Shift</h1>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-12 gap-6">
|
||||||
|
<div class="col-span-6">
|
||||||
|
<h2 class="text-lg font-medium">Cash In Denominations</h2>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
v-if="isValuesSeeded"
|
||||||
|
class="mt-4 text-base"
|
||||||
|
:df="getField('openingCash')"
|
||||||
|
:show-header="true"
|
||||||
|
:border="true"
|
||||||
|
:value="posShiftDoc?.openingCash"
|
||||||
|
@row-change="handleChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-6">
|
||||||
|
<h2 class="text-lg font-medium">Opening Amount</h2>
|
||||||
|
|
||||||
|
<Table
|
||||||
|
v-if="isValuesSeeded"
|
||||||
|
class="mt-4 text-base"
|
||||||
|
:df="getField('openingAmounts')"
|
||||||
|
:show-header="true"
|
||||||
|
:border="true"
|
||||||
|
:max-rows-before-overflow="4"
|
||||||
|
:value="posShiftDoc?.openingAmounts"
|
||||||
|
:read-only="true"
|
||||||
|
@row-change="handleChange"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="mt-4 grid grid-cols-2 gap-4 flex items-end">
|
||||||
|
<Button class="w-full py-5 bg-red-500" @click="$router.back()">
|
||||||
|
<slot>
|
||||||
|
<p class="uppercase text-lg text-white font-semibold">
|
||||||
|
{{ t`Back` }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button class="w-full py-5 bg-green-500" @click="handleSubmit">
|
||||||
|
<slot>
|
||||||
|
<p class="uppercase text-lg text-white font-semibold">
|
||||||
|
{{ t`Submit` }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Button from 'src/components/Button.vue';
|
||||||
|
import Modal from 'src/components/Modal.vue';
|
||||||
|
import Table from 'src/components/Controls/Table.vue';
|
||||||
|
import { AccountTypeEnum } from 'models/baseModels/Account/types';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
import { POSShift } from 'models/inventory/Point of Sale/POSShift';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import { fyo } from 'src/initFyo';
|
||||||
|
import { showToast } from 'src/utils/interactive';
|
||||||
|
import { t } from 'fyo';
|
||||||
|
import { ValidationError } from 'fyo/utils/errors';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'OpenPOSShift',
|
||||||
|
components: { Button, Modal, Table },
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
doc: computed(() => this.posShiftDoc),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
emits: ['toggleModal'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
posShiftDoc: undefined as POSShift | undefined,
|
||||||
|
|
||||||
|
isValuesSeeded: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getDefaultCashDenominations() {
|
||||||
|
return this.fyo.singles.Defaults?.posCashDenominations;
|
||||||
|
},
|
||||||
|
posCashAccount() {
|
||||||
|
return fyo.singles.POSSettings?.cashAccount;
|
||||||
|
},
|
||||||
|
posOpeningCashAmount(): Money {
|
||||||
|
return this.posShiftDoc?.openingCashAmount as Money;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
this.isValuesSeeded = false;
|
||||||
|
this.posShiftDoc = fyo.singles[ModelNameEnum.POSShift];
|
||||||
|
|
||||||
|
await this.seedDefaults();
|
||||||
|
this.isValuesSeeded = true;
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async seedDefaultCashDenomiations() {
|
||||||
|
if (!this.posShiftDoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.posShiftDoc.openingCash = [];
|
||||||
|
await this.posShiftDoc.sync();
|
||||||
|
|
||||||
|
const denominations = this.getDefaultCashDenominations;
|
||||||
|
|
||||||
|
if (!denominations) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const row of denominations) {
|
||||||
|
await this.posShiftDoc.append('openingCash', {
|
||||||
|
denomination: row.denomination,
|
||||||
|
count: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.posShiftDoc.sync();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async seedPaymentMethods() {
|
||||||
|
if (!this.posShiftDoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.posShiftDoc.openingAmounts = [];
|
||||||
|
await this.posShiftDoc.sync();
|
||||||
|
|
||||||
|
await this.posShiftDoc.set('openingAmounts', [
|
||||||
|
{
|
||||||
|
paymentMethod: 'Cash',
|
||||||
|
amount: fyo.pesa(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
paymentMethod: 'Transfer',
|
||||||
|
amount: fyo.pesa(0),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
await this.posShiftDoc.sync();
|
||||||
|
},
|
||||||
|
async seedDefaults() {
|
||||||
|
if (!!this.posShiftDoc?.isShiftOpen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.seedDefaultCashDenomiations();
|
||||||
|
await this.seedPaymentMethods();
|
||||||
|
},
|
||||||
|
getField(fieldname: string) {
|
||||||
|
return this.fyo.getField(ModelNameEnum.POSShift, fieldname);
|
||||||
|
},
|
||||||
|
setOpeningCashAmount() {
|
||||||
|
if (!this.posShiftDoc?.openingAmounts) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.posShiftDoc.openingAmounts.map((row) => {
|
||||||
|
if (row.paymentMethod === 'Cash') {
|
||||||
|
row.amount = this.posShiftDoc?.openingCashAmount as Money;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async handleChange() {
|
||||||
|
await this.posShiftDoc?.sync();
|
||||||
|
this.setOpeningCashAmount();
|
||||||
|
},
|
||||||
|
async handleSubmit() {
|
||||||
|
try {
|
||||||
|
if (this.posShiftDoc?.openingCashAmount.isNegative()) {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`Opening Cash Amount can not be negative.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.posShiftDoc?.setMultiple({
|
||||||
|
isShiftOpen: true,
|
||||||
|
openingDate: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.posShiftDoc?.sync();
|
||||||
|
|
||||||
|
if (!this.posShiftDoc?.openingCashAmount.isZero()) {
|
||||||
|
const jvDoc = fyo.doc.getNewDoc(ModelNameEnum.JournalEntry, {
|
||||||
|
entryType: 'Journal Entry',
|
||||||
|
});
|
||||||
|
|
||||||
|
await jvDoc.append('accounts', {
|
||||||
|
account: this.posCashAccount,
|
||||||
|
debit: this.posShiftDoc?.openingCashAmount as Money,
|
||||||
|
credit: this.fyo.pesa(0),
|
||||||
|
});
|
||||||
|
|
||||||
|
await jvDoc.append('accounts', {
|
||||||
|
account: AccountTypeEnum.Cash,
|
||||||
|
debit: this.fyo.pesa(0),
|
||||||
|
credit: this.posShiftDoc?.openingCashAmount as Money,
|
||||||
|
});
|
||||||
|
|
||||||
|
await (await jvDoc.sync()).submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('toggleModal', 'ShiftOpen');
|
||||||
|
} catch (error) {
|
||||||
|
showToast({
|
||||||
|
type: 'error',
|
||||||
|
message: t`${error as string}`,
|
||||||
|
duration: 'short',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
569
src/pages/POS/POS.vue
Normal file
569
src/pages/POS/POS.vue
Normal file
@ -0,0 +1,569 @@
|
|||||||
|
<template>
|
||||||
|
<div class="">
|
||||||
|
<PageHeader :title="t`Point of Sale`">
|
||||||
|
<slot>
|
||||||
|
<Button class="bg-red-500" @click="toggleModal('ShiftClose')">
|
||||||
|
<span class="font-medium text-white">{{ t`Close POS Shift ` }}</span>
|
||||||
|
</Button>
|
||||||
|
</slot>
|
||||||
|
</PageHeader>
|
||||||
|
|
||||||
|
<OpenPOSShiftModal
|
||||||
|
v-if="!isPosShiftOpen"
|
||||||
|
:open-modal="!isPosShiftOpen"
|
||||||
|
@toggle-modal="toggleModal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ClosePOSShiftModal
|
||||||
|
:open-modal="openShiftCloseModal"
|
||||||
|
@toggle-modal="toggleModal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PaymentModal
|
||||||
|
:open-modal="openPaymentModal"
|
||||||
|
@create-transaction="createTransaction"
|
||||||
|
@toggle-modal="toggleModal"
|
||||||
|
@set-cash-amount="setCashAmount"
|
||||||
|
@set-transfer-amount="setTransferAmount"
|
||||||
|
@set-transfer-ref-no="setTransferRefNo"
|
||||||
|
@set-transfer-clearance-date="setTransferClearanceDate"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="bg-gray-25 gap-2 grid grid-cols-12 p-4"
|
||||||
|
style="height: calc(100vh - var(--h-row-largest))"
|
||||||
|
>
|
||||||
|
<div class="bg-white border col-span-5 rounded-md">
|
||||||
|
<div class="rounded-md p-4 col-span-5">
|
||||||
|
<div class="flex gap-x-2">
|
||||||
|
<!-- Item Search -->
|
||||||
|
<Link
|
||||||
|
:class="
|
||||||
|
fyo.singles.InventorySettings?.enableBarcodes
|
||||||
|
? 'flex-shrink-0 w-2/3'
|
||||||
|
: 'w-full'
|
||||||
|
"
|
||||||
|
:df="{
|
||||||
|
label: t`Search an Item`,
|
||||||
|
fieldtype: 'Link',
|
||||||
|
fieldname: 'item',
|
||||||
|
target: 'Item',
|
||||||
|
}"
|
||||||
|
:border="true"
|
||||||
|
:value="itemSearchTerm"
|
||||||
|
@keyup.enter="
|
||||||
|
async () => await addItem(await getItem(itemSearchTerm))
|
||||||
|
"
|
||||||
|
@change="(item: string) =>itemSearchTerm= item"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Barcode
|
||||||
|
v-if="fyo.singles.InventorySettings?.enableBarcodes"
|
||||||
|
class="w-1/3"
|
||||||
|
@item-selected="
|
||||||
|
async (name: string) => {
|
||||||
|
await addItem(await getItem(name));
|
||||||
|
}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ItemsTable @add-item="addItem" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-7">
|
||||||
|
<div class="flex flex-col gap-3" style="height: calc(100vh - 6rem)">
|
||||||
|
<div class="bg-white border grow h-full p-4 rounded-md">
|
||||||
|
<!-- Customer Search -->
|
||||||
|
<Link
|
||||||
|
v-if="sinvDoc.fieldMap"
|
||||||
|
class="flex-shrink-0"
|
||||||
|
:border="true"
|
||||||
|
:value="sinvDoc.party"
|
||||||
|
:df="sinvDoc.fieldMap.party"
|
||||||
|
@change="(value:string) => (sinvDoc.party = value)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SelectedItemTable />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white border p-4 rounded-md">
|
||||||
|
<div class="w-full grid grid-cols-2 gap-y-2 gap-x-3">
|
||||||
|
<div class="">
|
||||||
|
<div class="grid grid-cols-2 gap-2">
|
||||||
|
<FloatingLabelFloatInput
|
||||||
|
:df="{
|
||||||
|
label: t`Total Quantity`,
|
||||||
|
fieldtype: 'Int',
|
||||||
|
fieldname: 'totalQuantity',
|
||||||
|
minvalue: 0,
|
||||||
|
maxvalue: 1000,
|
||||||
|
}"
|
||||||
|
size="large"
|
||||||
|
:value="totalQuantity"
|
||||||
|
:read-only="true"
|
||||||
|
:text-right="true"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FloatingLabelCurrencyInput
|
||||||
|
:df="{
|
||||||
|
label: t`Add'l Discounts`,
|
||||||
|
fieldtype: 'Int',
|
||||||
|
fieldname: 'additionalDiscount',
|
||||||
|
minvalue: 0,
|
||||||
|
}"
|
||||||
|
size="large"
|
||||||
|
:value="additionalDiscounts"
|
||||||
|
:read-only="true"
|
||||||
|
:text-right="true"
|
||||||
|
@change="(amount:Money)=> additionalDiscounts= amount"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 grid grid-cols-2 gap-2">
|
||||||
|
<FloatingLabelCurrencyInput
|
||||||
|
:df="{
|
||||||
|
label: t`Item Discounts`,
|
||||||
|
fieldtype: 'Currency',
|
||||||
|
fieldname: 'itemDiscounts',
|
||||||
|
}"
|
||||||
|
size="large"
|
||||||
|
:value="itemDiscounts"
|
||||||
|
:read-only="true"
|
||||||
|
:text-right="true"
|
||||||
|
/>
|
||||||
|
<FloatingLabelCurrencyInput
|
||||||
|
v-if="sinvDoc.fieldMap"
|
||||||
|
:df="sinvDoc.fieldMap.grandTotal"
|
||||||
|
size="large"
|
||||||
|
:value="sinvDoc.grandTotal"
|
||||||
|
:read-only="true"
|
||||||
|
:text-right="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="">
|
||||||
|
<Button
|
||||||
|
class="w-full bg-red-500 py-6"
|
||||||
|
:disabled="!sinvDoc.items?.length"
|
||||||
|
@click="clearValues"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<p class="uppercase text-lg text-white font-semibold">
|
||||||
|
{{ t`Cancel` }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
class="mt-4 w-full bg-green-500 py-6"
|
||||||
|
:disabled="disablePayButton"
|
||||||
|
@click="toggleModal('Payment', true)"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<p class="uppercase text-lg text-white font-semibold">
|
||||||
|
{{ t`Pay` }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Button from 'src/components/Button.vue';
|
||||||
|
import ClosePOSShiftModal from './ClosePOSShiftModal.vue';
|
||||||
|
import FloatingLabelCurrencyInput from 'src/components/POS/FloatingLabelCurrencyInput.vue';
|
||||||
|
import FloatingLabelFloatInput from 'src/components/POS/FloatingLabelFloatInput.vue';
|
||||||
|
import ItemsTable from 'src/components/POS/ItemsTable.vue';
|
||||||
|
import Link from 'src/components/Controls/Link.vue';
|
||||||
|
import OpenPOSShiftModal from './OpenPOSShiftModal.vue';
|
||||||
|
import PageHeader from 'src/components/PageHeader.vue';
|
||||||
|
import PaymentModal from './PaymentModal.vue';
|
||||||
|
import SelectedItemTable from 'src/components/POS/SelectedItemTable.vue';
|
||||||
|
import { computed, defineComponent } from 'vue';
|
||||||
|
import { fyo } from 'src/initFyo';
|
||||||
|
import { routeTo, toggleSidebar } from 'src/utils/ui';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||||
|
import { t } from 'fyo';
|
||||||
|
import {
|
||||||
|
ItemQtyMap,
|
||||||
|
ItemSerialNumbers,
|
||||||
|
POSItem,
|
||||||
|
} from 'src/components/POS/types';
|
||||||
|
import { Item } from 'models/baseModels/Item/Item';
|
||||||
|
import { ModalName } from 'src/components/POS/types';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
import { Payment } from 'models/baseModels/Payment/Payment';
|
||||||
|
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||||
|
import { Shipment } from 'models/inventory/Shipment';
|
||||||
|
import { showToast } from 'src/utils/interactive';
|
||||||
|
import {
|
||||||
|
getItem,
|
||||||
|
getItemDiscounts,
|
||||||
|
getItemQtyMap,
|
||||||
|
getTotalQuantity,
|
||||||
|
getTotalTaxedAmount,
|
||||||
|
validateIsPosSettingsSet,
|
||||||
|
validateShipment,
|
||||||
|
validateSinv,
|
||||||
|
} from 'src/utils/pos';
|
||||||
|
import Barcode from 'src/components/Controls/Barcode.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'POS',
|
||||||
|
components: {
|
||||||
|
Button,
|
||||||
|
ClosePOSShiftModal,
|
||||||
|
FloatingLabelCurrencyInput,
|
||||||
|
FloatingLabelFloatInput,
|
||||||
|
ItemsTable,
|
||||||
|
Link,
|
||||||
|
OpenPOSShiftModal,
|
||||||
|
PageHeader,
|
||||||
|
PaymentModal,
|
||||||
|
SelectedItemTable,
|
||||||
|
Barcode,
|
||||||
|
},
|
||||||
|
provide() {
|
||||||
|
return {
|
||||||
|
cashAmount: computed(() => this.cashAmount),
|
||||||
|
doc: computed(() => this.sinvDoc),
|
||||||
|
isDiscountingEnabled: computed(() => this.isDiscountingEnabled),
|
||||||
|
itemDiscounts: computed(() => this.itemDiscounts),
|
||||||
|
itemQtyMap: computed(() => this.itemQtyMap),
|
||||||
|
itemSerialNumbers: computed(() => this.itemSerialNumbers),
|
||||||
|
sinvDoc: computed(() => this.sinvDoc),
|
||||||
|
totalTaxedAmount: computed(() => this.totalTaxedAmount),
|
||||||
|
transferAmount: computed(() => this.transferAmount),
|
||||||
|
transferClearanceDate: computed(() => this.transferClearanceDate),
|
||||||
|
transferRefNo: computed(() => this.transferRefNo),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isItemsSeeded: false,
|
||||||
|
openPaymentModal: false,
|
||||||
|
openShiftCloseModal: false,
|
||||||
|
openShiftOpenModal: false,
|
||||||
|
|
||||||
|
additionalDiscounts: fyo.pesa(0),
|
||||||
|
cashAmount: fyo.pesa(0),
|
||||||
|
itemDiscounts: fyo.pesa(0),
|
||||||
|
totalTaxedAmount: fyo.pesa(0),
|
||||||
|
transferAmount: fyo.pesa(0),
|
||||||
|
|
||||||
|
totalQuantity: 0,
|
||||||
|
|
||||||
|
defaultCustomer: undefined as string | undefined,
|
||||||
|
itemSearchTerm: '',
|
||||||
|
transferRefNo: undefined as string | undefined,
|
||||||
|
|
||||||
|
transferClearanceDate: undefined as Date | undefined,
|
||||||
|
|
||||||
|
itemQtyMap: {} as ItemQtyMap,
|
||||||
|
itemSerialNumbers: {} as ItemSerialNumbers,
|
||||||
|
paymentDoc: {} as Payment,
|
||||||
|
sinvDoc: {} as SalesInvoice,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
defaultPOSCashAccount: () =>
|
||||||
|
fyo.singles.POSSettings?.cashAccount ?? undefined,
|
||||||
|
isDiscountingEnabled(): boolean {
|
||||||
|
return !!fyo.singles.AccountingSettings?.enableDiscounting;
|
||||||
|
},
|
||||||
|
isPosShiftOpen: () => !!fyo.singles.POSShift?.isShiftOpen,
|
||||||
|
isPaymentAmountSet(): boolean {
|
||||||
|
if (this.sinvDoc.grandTotal?.isZero()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cashAmount.isZero() && this.transferAmount.isZero()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
disablePayButton(): boolean {
|
||||||
|
if (!this.sinvDoc.items?.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.sinvDoc.party) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
sinvDoc: {
|
||||||
|
handler() {
|
||||||
|
this.updateValues();
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async activated() {
|
||||||
|
toggleSidebar(false);
|
||||||
|
validateIsPosSettingsSet(fyo);
|
||||||
|
this.setSinvDoc();
|
||||||
|
this.setDefaultCustomer();
|
||||||
|
await this.setItemQtyMap();
|
||||||
|
},
|
||||||
|
deactivated() {
|
||||||
|
toggleSidebar(true);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setCashAmount(amount: Money) {
|
||||||
|
this.cashAmount = amount;
|
||||||
|
},
|
||||||
|
setDefaultCustomer() {
|
||||||
|
this.defaultCustomer = this.fyo.singles.Defaults?.posCustomer ?? '';
|
||||||
|
this.sinvDoc.party = this.defaultCustomer;
|
||||||
|
},
|
||||||
|
setItemDiscounts() {
|
||||||
|
this.itemDiscounts = getItemDiscounts(
|
||||||
|
this.sinvDoc.items as SalesInvoiceItem[]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
async setItemQtyMap() {
|
||||||
|
this.itemQtyMap = await getItemQtyMap();
|
||||||
|
},
|
||||||
|
setSinvDoc() {
|
||||||
|
this.sinvDoc = this.fyo.doc.getNewDoc(ModelNameEnum.SalesInvoice, {
|
||||||
|
account: 'Debtors',
|
||||||
|
party: this.sinvDoc.party ?? this.defaultCustomer,
|
||||||
|
isPOS: true,
|
||||||
|
}) as SalesInvoice;
|
||||||
|
},
|
||||||
|
setTotalQuantity() {
|
||||||
|
this.totalQuantity = getTotalQuantity(
|
||||||
|
this.sinvDoc.items as SalesInvoiceItem[]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
setTotalTaxedAmount() {
|
||||||
|
this.totalTaxedAmount = getTotalTaxedAmount(this.sinvDoc as SalesInvoice);
|
||||||
|
},
|
||||||
|
setTransferAmount(amount: Money = fyo.pesa(0)) {
|
||||||
|
this.transferAmount = amount;
|
||||||
|
},
|
||||||
|
setTransferClearanceDate(date: Date) {
|
||||||
|
this.transferClearanceDate = date;
|
||||||
|
},
|
||||||
|
setTransferRefNo(ref: string) {
|
||||||
|
this.transferRefNo = ref;
|
||||||
|
},
|
||||||
|
|
||||||
|
async addItem(item: POSItem | Item | undefined) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
this.sinvDoc.runFormulas();
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!this.itemQtyMap[item.name as string] ||
|
||||||
|
this.itemQtyMap[item.name as string].availableQty === 0
|
||||||
|
) {
|
||||||
|
showToast({
|
||||||
|
type: 'error',
|
||||||
|
message: t`Item ${item.name as string} has Zero Quantity`,
|
||||||
|
duration: 'short',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingItems =
|
||||||
|
this.sinvDoc.items?.filter(
|
||||||
|
(invoiceItem) => invoiceItem.item === item.name
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
|
if (item.hasBatch) {
|
||||||
|
for (const item of existingItems) {
|
||||||
|
const itemQty = item.quantity ?? 0;
|
||||||
|
const qtyInBatch =
|
||||||
|
this.itemQtyMap[item.item as string][item.batch as string] ?? 0;
|
||||||
|
|
||||||
|
if (itemQty < qtyInBatch) {
|
||||||
|
item.quantity = (item.quantity as number) + 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.sinvDoc.append('items', {
|
||||||
|
rate: item.rate as Money,
|
||||||
|
item: item.name,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
showToast({
|
||||||
|
type: 'error',
|
||||||
|
message: t`${error as string}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingItems.length) {
|
||||||
|
existingItems[0].quantity = (existingItems[0].quantity as number) + 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.sinvDoc.append('items', {
|
||||||
|
rate: item.rate as Money,
|
||||||
|
item: item.name,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async createTransaction(shouldPrint = false) {
|
||||||
|
try {
|
||||||
|
await this.validate();
|
||||||
|
await this.submitSinvDoc(shouldPrint);
|
||||||
|
await this.makePayment();
|
||||||
|
await this.makeStockTransfer();
|
||||||
|
await this.afterTransaction();
|
||||||
|
} catch (error) {
|
||||||
|
showToast({
|
||||||
|
type: 'error',
|
||||||
|
message: t`${error as string}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async makePayment() {
|
||||||
|
this.paymentDoc = this.sinvDoc.getPayment() as Payment;
|
||||||
|
const paymentMethod = this.cashAmount.isZero() ? 'Transfer' : 'Cash';
|
||||||
|
await this.paymentDoc.set('paymentMethod', paymentMethod);
|
||||||
|
|
||||||
|
if (paymentMethod === 'Transfer') {
|
||||||
|
await this.paymentDoc.setMultiple({
|
||||||
|
amount: this.transferAmount as Money,
|
||||||
|
referenceId: this.transferRefNo,
|
||||||
|
clearanceDate: this.transferClearanceDate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (paymentMethod === 'Cash') {
|
||||||
|
await this.paymentDoc.setMultiple({
|
||||||
|
paymentAccount: this.defaultPOSCashAccount,
|
||||||
|
amount: this.cashAmount as Money,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.paymentDoc.once('afterSubmit', () => {
|
||||||
|
showToast({
|
||||||
|
type: 'success',
|
||||||
|
message: t`Payment ${this.paymentDoc.name as string} is Saved`,
|
||||||
|
duration: 'short',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.paymentDoc?.sync();
|
||||||
|
await this.paymentDoc?.submit();
|
||||||
|
} catch (error) {
|
||||||
|
return showToast({
|
||||||
|
type: 'error',
|
||||||
|
message: t`${error as string}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async makeStockTransfer() {
|
||||||
|
const shipmentDoc = (await this.sinvDoc.getStockTransfer()) as Shipment;
|
||||||
|
if (!shipmentDoc.items) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of shipmentDoc.items) {
|
||||||
|
item.location = fyo.singles.POSSettings?.inventory;
|
||||||
|
item.serialNumber =
|
||||||
|
this.itemSerialNumbers[item.item as string] ?? undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
shipmentDoc.once('afterSubmit', () => {
|
||||||
|
showToast({
|
||||||
|
type: 'success',
|
||||||
|
message: t`Shipment ${shipmentDoc.name as string} is Submitted`,
|
||||||
|
duration: 'short',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await shipmentDoc.sync();
|
||||||
|
await shipmentDoc.submit();
|
||||||
|
} catch (error) {
|
||||||
|
return showToast({
|
||||||
|
type: 'error',
|
||||||
|
message: t`${error as string}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async submitSinvDoc(shouldPrint: boolean) {
|
||||||
|
this.sinvDoc.once('afterSubmit', async () => {
|
||||||
|
showToast({
|
||||||
|
type: 'success',
|
||||||
|
message: t`Sales Invoice ${this.sinvDoc.name as string} is Submitted`,
|
||||||
|
duration: 'short',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (shouldPrint) {
|
||||||
|
await routeTo(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
||||||
|
`/print/${this.sinvDoc.schemaName}/${this.sinvDoc.name}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.validate();
|
||||||
|
await this.sinvDoc.runFormulas();
|
||||||
|
await this.sinvDoc.sync();
|
||||||
|
await this.sinvDoc.submit();
|
||||||
|
} catch (error) {
|
||||||
|
return showToast({
|
||||||
|
type: 'error',
|
||||||
|
message: t`${error as string}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async afterTransaction() {
|
||||||
|
await this.setItemQtyMap();
|
||||||
|
this.clearValues();
|
||||||
|
this.setSinvDoc();
|
||||||
|
this.toggleModal('Payment', false);
|
||||||
|
},
|
||||||
|
clearValues() {
|
||||||
|
this.setSinvDoc();
|
||||||
|
this.itemSerialNumbers = {};
|
||||||
|
|
||||||
|
this.cashAmount = fyo.pesa(0);
|
||||||
|
this.transferAmount = fyo.pesa(0);
|
||||||
|
},
|
||||||
|
toggleModal(modal: ModalName, value?: boolean) {
|
||||||
|
if (value) {
|
||||||
|
return (this[`open${modal}Modal`] = value);
|
||||||
|
}
|
||||||
|
return (this[`open${modal}Modal`] = !this[`open${modal}Modal`]);
|
||||||
|
},
|
||||||
|
updateValues() {
|
||||||
|
this.setTotalQuantity();
|
||||||
|
this.setItemDiscounts();
|
||||||
|
this.setTotalTaxedAmount();
|
||||||
|
},
|
||||||
|
async validate() {
|
||||||
|
validateSinv(this.sinvDoc as SalesInvoice, this.itemQtyMap);
|
||||||
|
await validateShipment(this.itemSerialNumbers);
|
||||||
|
},
|
||||||
|
|
||||||
|
getItem,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
325
src/pages/POS/PaymentModal.vue
Normal file
325
src/pages/POS/PaymentModal.vue
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
<template>
|
||||||
|
<Modal class="w-2/6 ml-auto mr-3.5" :set-close-listener="false">
|
||||||
|
<div v-if="sinvDoc.fieldMap" class="px-4 py-6 grid" style="height: 95vh">
|
||||||
|
<div class="grid grid-cols-2 gap-6">
|
||||||
|
<Currency
|
||||||
|
:df="fyo.fieldMap.PaymentFor.amount"
|
||||||
|
:read-only="!transferAmount.isZero()"
|
||||||
|
:border="true"
|
||||||
|
:text-right="true"
|
||||||
|
:value="cashAmount"
|
||||||
|
@change="(amount:Money)=> $emit('setCashAmount', amount)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
class="w-full py-5 bg-teal-500"
|
||||||
|
@click="setCashOrTransferAmount"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<p class="uppercase text-lg text-white font-semibold">
|
||||||
|
{{ t`Cash` }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Currency
|
||||||
|
:df="fyo.fieldMap.PaymentFor.amount"
|
||||||
|
:read-only="!cashAmount.isZero()"
|
||||||
|
:border="true"
|
||||||
|
:text-right="true"
|
||||||
|
:value="transferAmount"
|
||||||
|
@change="(value:Money)=> $emit('setTransferAmount', value)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
class="w-full py-5 bg-teal-500"
|
||||||
|
@click="setCashOrTransferAmount('Transfer')"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<p class="uppercase text-lg text-white font-semibold">
|
||||||
|
{{ t`Transfer` }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8 grid grid-cols-2 gap-6">
|
||||||
|
<Data
|
||||||
|
v-show="!transferAmount.isZero()"
|
||||||
|
:df="fyo.fieldMap.Payment.referenceId"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:required="!transferAmount.isZero()"
|
||||||
|
:value="transferRefNo"
|
||||||
|
@change="(value:string) => $emit('setTransferRefNo', value)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Date
|
||||||
|
v-show="!transferAmount.isZero()"
|
||||||
|
:df="fyo.fieldMap.Payment.clearanceDate"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:required="!transferAmount.isZero()"
|
||||||
|
:value="transferClearanceDate"
|
||||||
|
@change="(value:Date) => $emit('setTransferClearanceDate', value)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-14 grid grid-cols-2 gap-6">
|
||||||
|
<Currency
|
||||||
|
v-show="showPaidChange"
|
||||||
|
:df="{
|
||||||
|
label: t`Paid Change`,
|
||||||
|
fieldtype: 'Currency',
|
||||||
|
fieldname: 'paidChange',
|
||||||
|
}"
|
||||||
|
:read-only="true"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:text-right="true"
|
||||||
|
:value="paidChange"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Currency
|
||||||
|
v-show="showBalanceAmount"
|
||||||
|
:df="{
|
||||||
|
label: t`Balance Amount`,
|
||||||
|
fieldtype: 'Currency',
|
||||||
|
fieldname: 'balanceAmount',
|
||||||
|
}"
|
||||||
|
:read-only="true"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:text-right="true"
|
||||||
|
:value="balanceAmount"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="mb-14 row-start-4 row-span-2 grid grid-cols-2 gap-x-6 gap-y-11"
|
||||||
|
>
|
||||||
|
<Currency
|
||||||
|
:df="sinvDoc.fieldMap.netTotal"
|
||||||
|
:read-only="true"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:text-right="true"
|
||||||
|
:value="sinvDoc?.netTotal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Currency
|
||||||
|
:df="{
|
||||||
|
label: t`Taxes and Charges`,
|
||||||
|
fieldtype: 'Currency',
|
||||||
|
fieldname: 'taxesAndCharges',
|
||||||
|
}"
|
||||||
|
:read-only="true"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:text-right="true"
|
||||||
|
:value="totalTaxedAmount"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Currency
|
||||||
|
:df="sinvDoc.fieldMap.baseGrandTotal"
|
||||||
|
:read-only="true"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:text-right="true"
|
||||||
|
:value="sinvDoc?.baseGrandTotal"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Currency
|
||||||
|
v-if="isDiscountingEnabled"
|
||||||
|
:df="sinvDoc.fieldMap.discountAmount"
|
||||||
|
:read-only="true"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:text-right="true"
|
||||||
|
:value="itemDiscounts"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Currency
|
||||||
|
:df="sinvDoc.fieldMap.grandTotal"
|
||||||
|
:read-only="true"
|
||||||
|
:show-label="true"
|
||||||
|
:border="true"
|
||||||
|
:text-right="true"
|
||||||
|
:value="sinvDoc?.grandTotal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row-start-6 grid grid-cols-2 gap-4 mt-auto">
|
||||||
|
<div class="col-span-2">
|
||||||
|
<Button
|
||||||
|
class="w-full bg-red-500"
|
||||||
|
style="padding: 1.35rem"
|
||||||
|
@click="$emit('toggleModal', 'Payment')"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<p class="uppercase text-lg text-white font-semibold">
|
||||||
|
{{ t`Cancel` }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-span-1">
|
||||||
|
<Button
|
||||||
|
class="w-full bg-blue-500"
|
||||||
|
style="padding: 1.35rem"
|
||||||
|
:disabled="disableSubmitButton"
|
||||||
|
@click="$emit('createTransaction')"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<p class="uppercase text-lg text-white font-semibold">
|
||||||
|
{{ t`Submit` }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="col-span-1">
|
||||||
|
<Button
|
||||||
|
class="w-full bg-green-500"
|
||||||
|
style="padding: 1.35rem"
|
||||||
|
:disabled="disableSubmitButton"
|
||||||
|
@click="$emit('createTransaction', true)"
|
||||||
|
>
|
||||||
|
<slot>
|
||||||
|
<p class="uppercase text-lg text-white font-semibold">
|
||||||
|
{{ t`Submit & Print` }}
|
||||||
|
</p>
|
||||||
|
</slot>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Button from 'src/components/Button.vue';
|
||||||
|
import Currency from 'src/components/Controls/Currency.vue';
|
||||||
|
import Data from 'src/components/Controls/Data.vue';
|
||||||
|
import Date from 'src/components/Controls/Date.vue';
|
||||||
|
import Modal from 'src/components/Modal.vue';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||||
|
import { defineComponent, inject } from 'vue';
|
||||||
|
import { fyo } from 'src/initFyo';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'PaymentModal',
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
Currency,
|
||||||
|
Button,
|
||||||
|
Data,
|
||||||
|
Date,
|
||||||
|
},
|
||||||
|
emits: [
|
||||||
|
'createTransaction',
|
||||||
|
'setCashAmount',
|
||||||
|
'setTransferAmount',
|
||||||
|
'setTransferClearanceDate',
|
||||||
|
'setTransferRefNo',
|
||||||
|
'toggleModal',
|
||||||
|
],
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
cashAmount: inject('cashAmount') as Money,
|
||||||
|
isDiscountingEnabled: inject('isDiscountingEnabled') as boolean,
|
||||||
|
itemDiscounts: inject('itemDiscounts') as Money,
|
||||||
|
transferAmount: inject('transferAmount') as Money,
|
||||||
|
sinvDoc: inject('sinvDoc') as SalesInvoice,
|
||||||
|
transferRefNo: inject('transferRefNo') as string,
|
||||||
|
transferClearanceDate: inject('transferClearanceDate') as Date,
|
||||||
|
totalTaxedAmount: inject('totalTaxedAmount') as Money,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
balanceAmount(): Money {
|
||||||
|
const grandTotal = this.sinvDoc?.grandTotal ?? fyo.pesa(0);
|
||||||
|
|
||||||
|
if (this.cashAmount.isZero()) {
|
||||||
|
return grandTotal.sub(this.transferAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return grandTotal.sub(this.cashAmount);
|
||||||
|
},
|
||||||
|
paidChange(): Money {
|
||||||
|
const grandTotal = this.sinvDoc?.grandTotal ?? fyo.pesa(0);
|
||||||
|
|
||||||
|
if (this.cashAmount.isZero()) {
|
||||||
|
return this.transferAmount.sub(grandTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.cashAmount.sub(grandTotal);
|
||||||
|
},
|
||||||
|
showBalanceAmount(): boolean {
|
||||||
|
if (
|
||||||
|
this.cashAmount.eq(fyo.pesa(0)) &&
|
||||||
|
this.transferAmount.eq(fyo.pesa(0))
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cashAmount.gte(this.sinvDoc?.grandTotal ?? fyo.pesa(0))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.transferAmount.gte(this.sinvDoc?.grandTotal ?? fyo.pesa(0))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
showPaidChange(): boolean {
|
||||||
|
if (
|
||||||
|
this.cashAmount.eq(fyo.pesa(0)) &&
|
||||||
|
this.transferAmount.eq(fyo.pesa(0))
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cashAmount.gt(this.sinvDoc?.grandTotal ?? fyo.pesa(0))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.transferAmount.gt(this.sinvDoc?.grandTotal ?? fyo.pesa(0))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
disableSubmitButton(): boolean {
|
||||||
|
if (
|
||||||
|
!this.sinvDoc.grandTotal?.isZero() &&
|
||||||
|
this.transferAmount.isZero() &&
|
||||||
|
this.cashAmount.isZero()
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.cashAmount.isZero() &&
|
||||||
|
(!this.transferRefNo || !this.transferClearanceDate)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setCashOrTransferAmount(paymentMethod = 'Cash') {
|
||||||
|
if (paymentMethod === 'Transfer') {
|
||||||
|
this.$emit('setCashAmount', fyo.pesa(0));
|
||||||
|
this.$emit('setTransferAmount', this.sinvDoc?.grandTotal);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$emit('setTransferAmount', fyo.pesa(0));
|
||||||
|
this.$emit('setCashAmount', this.sinvDoc?.grandTotal);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
@ -115,6 +115,7 @@ export default defineComponent({
|
|||||||
ModelNameEnum.AccountingSettings,
|
ModelNameEnum.AccountingSettings,
|
||||||
ModelNameEnum.InventorySettings,
|
ModelNameEnum.InventorySettings,
|
||||||
ModelNameEnum.Defaults,
|
ModelNameEnum.Defaults,
|
||||||
|
ModelNameEnum.POSSettings,
|
||||||
ModelNameEnum.PrintSettings,
|
ModelNameEnum.PrintSettings,
|
||||||
ModelNameEnum.SystemSettings,
|
ModelNameEnum.SystemSettings,
|
||||||
].some((s) => this.fyo.singles[s]?.canSave);
|
].some((s) => this.fyo.singles[s]?.canSave);
|
||||||
@ -133,6 +134,7 @@ export default defineComponent({
|
|||||||
[ModelNameEnum.PrintSettings]: this.t`Print`,
|
[ModelNameEnum.PrintSettings]: this.t`Print`,
|
||||||
[ModelNameEnum.InventorySettings]: this.t`Inventory`,
|
[ModelNameEnum.InventorySettings]: this.t`Inventory`,
|
||||||
[ModelNameEnum.Defaults]: this.t`Defaults`,
|
[ModelNameEnum.Defaults]: this.t`Defaults`,
|
||||||
|
[ModelNameEnum.POSSettings]: this.t`POS Settings`,
|
||||||
[ModelNameEnum.SystemSettings]: this.t`System`,
|
[ModelNameEnum.SystemSettings]: this.t`System`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -140,16 +142,26 @@ export default defineComponent({
|
|||||||
const enableInventory =
|
const enableInventory =
|
||||||
!!this.fyo.singles.AccountingSettings?.enableInventory;
|
!!this.fyo.singles.AccountingSettings?.enableInventory;
|
||||||
|
|
||||||
|
const enablePOS = !!this.fyo.singles.InventorySettings?.enablePointOfSale;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
ModelNameEnum.AccountingSettings,
|
ModelNameEnum.AccountingSettings,
|
||||||
ModelNameEnum.InventorySettings,
|
ModelNameEnum.InventorySettings,
|
||||||
ModelNameEnum.Defaults,
|
ModelNameEnum.Defaults,
|
||||||
|
ModelNameEnum.POSSettings,
|
||||||
ModelNameEnum.PrintSettings,
|
ModelNameEnum.PrintSettings,
|
||||||
ModelNameEnum.SystemSettings,
|
ModelNameEnum.SystemSettings,
|
||||||
]
|
]
|
||||||
.filter((s) =>
|
.filter((s) => {
|
||||||
s === ModelNameEnum.InventorySettings ? enableInventory : true
|
if (s === ModelNameEnum.InventorySettings && !enableInventory) {
|
||||||
)
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s === ModelNameEnum.POSSettings && !enablePOS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
.map((s) => this.fyo.schemaMap[s]!);
|
.map((s) => this.fyo.schemaMap[s]!);
|
||||||
},
|
},
|
||||||
activeGroup(): Map<string, Field[]> {
|
activeGroup(): Map<string, Field[]> {
|
||||||
|
67
src/pages/TemplateBuilder/SetType.vue
Normal file
67
src/pages/TemplateBuilder/SetType.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-form">
|
||||||
|
<FormHeader :form-title="t`Set Print Size`" />
|
||||||
|
<hr />
|
||||||
|
<div class="p-4 w-full flex flex-col gap-4">
|
||||||
|
<p class="text-base text-gray-900">
|
||||||
|
{{ t`Select the template type.` }}
|
||||||
|
</p>
|
||||||
|
<Select
|
||||||
|
:df="df"
|
||||||
|
:value="type"
|
||||||
|
:border="true"
|
||||||
|
:show-label="true"
|
||||||
|
@change="typeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex border-t p-4">
|
||||||
|
<Button class="ml-auto" type="primary" @click="done">{{
|
||||||
|
t`Done`
|
||||||
|
}}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { PrintTemplate } from 'models/baseModels/PrintTemplate';
|
||||||
|
import { OptionField } from 'schemas/types';
|
||||||
|
import Button from 'src/components/Button.vue';
|
||||||
|
import Select from 'src/components/Controls/Select.vue';
|
||||||
|
import FormHeader from 'src/components/FormHeader.vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { FormHeader, Select, Button },
|
||||||
|
props: { doc: { type: PrintTemplate, required: true } },
|
||||||
|
emits: ['done'],
|
||||||
|
data() {
|
||||||
|
return { type: 'SalesInvoice' };
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
df(): OptionField {
|
||||||
|
const options = PrintTemplate.lists.type(this.doc);
|
||||||
|
return {
|
||||||
|
...fyo.getField('PrintTemplate', 'type'),
|
||||||
|
options,
|
||||||
|
fieldtype: 'Select',
|
||||||
|
default: options[0].value,
|
||||||
|
} as OptionField;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.type = this.doc.type ?? 'SalesInvoice';
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
typeChange(v: string) {
|
||||||
|
if (this.type === v) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.type = v;
|
||||||
|
},
|
||||||
|
async done() {
|
||||||
|
await this.doc.set('type', this.type);
|
||||||
|
this.$emit('done');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
@ -213,6 +213,13 @@
|
|||||||
>
|
>
|
||||||
<SetPrintSize :doc="doc" @done="showSizeModal = !showSizeModal" />
|
<SetPrintSize :doc="doc" @done="showSizeModal = !showSizeModal" />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<Modal
|
||||||
|
v-if="doc"
|
||||||
|
:open-modal="showTypeModal"
|
||||||
|
@closemodal="showTypeModal = !showTypeModal"
|
||||||
|
>
|
||||||
|
<SetType :doc="doc" @done="showTypeModal = !showTypeModal" />
|
||||||
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@ -256,6 +263,7 @@ import { getMapFromList } from 'utils/index';
|
|||||||
import { computed, defineComponent, inject, ref } from 'vue';
|
import { computed, defineComponent, inject, ref } from 'vue';
|
||||||
import PrintContainer from './PrintContainer.vue';
|
import PrintContainer from './PrintContainer.vue';
|
||||||
import SetPrintSize from './SetPrintSize.vue';
|
import SetPrintSize from './SetPrintSize.vue';
|
||||||
|
import SetType from './SetType.vue';
|
||||||
import TemplateBuilderHint from './TemplateBuilderHint.vue';
|
import TemplateBuilderHint from './TemplateBuilderHint.vue';
|
||||||
import TemplateEditor from './TemplateEditor.vue';
|
import TemplateEditor from './TemplateEditor.vue';
|
||||||
|
|
||||||
@ -273,6 +281,7 @@ export default defineComponent({
|
|||||||
Link,
|
Link,
|
||||||
Modal,
|
Modal,
|
||||||
SetPrintSize,
|
SetPrintSize,
|
||||||
|
SetType,
|
||||||
},
|
},
|
||||||
provide() {
|
provide() {
|
||||||
return { doc: computed(() => this.doc) };
|
return { doc: computed(() => this.doc) };
|
||||||
@ -303,6 +312,7 @@ export default defineComponent({
|
|||||||
scale: 0.6,
|
scale: 0.6,
|
||||||
panelWidth: 22 /** rem */ * 16 /** px */,
|
panelWidth: 22 /** rem */ * 16 /** px */,
|
||||||
templateChanged: false,
|
templateChanged: false,
|
||||||
|
showTypeModal: false,
|
||||||
showSizeModal: false,
|
showSizeModal: false,
|
||||||
preEditMode: {
|
preEditMode: {
|
||||||
scale: 0.6,
|
scale: 0.6,
|
||||||
@ -315,6 +325,7 @@ export default defineComponent({
|
|||||||
hints?: PrintTemplateHint;
|
hints?: PrintTemplateHint;
|
||||||
values: null | PrintValues;
|
values: null | PrintValues;
|
||||||
displayDoc: PrintTemplate | null;
|
displayDoc: PrintTemplate | null;
|
||||||
|
showTypeModal: boolean;
|
||||||
showSizeModal: boolean;
|
showSizeModal: boolean;
|
||||||
scale: number;
|
scale: number;
|
||||||
panelWidth: number;
|
panelWidth: number;
|
||||||
@ -367,6 +378,14 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.doc.isCustom && !this.showTypeModal) {
|
||||||
|
actions.push({
|
||||||
|
label: this.t`Set Template Type`,
|
||||||
|
group: this.t`Action`,
|
||||||
|
action: () => (this.showTypeModal = true),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (this.doc.isCustom && !this.showSizeModal) {
|
if (this.doc.isCustom && !this.showSizeModal) {
|
||||||
actions.push({
|
actions.push({
|
||||||
label: this.t`Set Print Size`,
|
label: this.t`Set Print Size`,
|
||||||
|
@ -11,6 +11,7 @@ import Report from 'src/pages/Report.vue';
|
|||||||
import Settings from 'src/pages/Settings/Settings.vue';
|
import Settings from 'src/pages/Settings/Settings.vue';
|
||||||
import TemplateBuilder from 'src/pages/TemplateBuilder/TemplateBuilder.vue';
|
import TemplateBuilder from 'src/pages/TemplateBuilder/TemplateBuilder.vue';
|
||||||
import CustomizeForm from 'src/pages/CustomizeForm/CustomizeForm.vue';
|
import CustomizeForm from 'src/pages/CustomizeForm/CustomizeForm.vue';
|
||||||
|
import POS from 'src/pages/POS/POS.vue';
|
||||||
import type { HistoryState } from 'vue-router';
|
import type { HistoryState } from 'vue-router';
|
||||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||||
import { historyState } from './utils/refs';
|
import { historyState } from './utils/refs';
|
||||||
@ -124,6 +125,18 @@ const routes: RouteRecordRaw[] = [
|
|||||||
edit: (route) => route.query,
|
edit: (route) => route.query,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/pos',
|
||||||
|
name: 'Point of Sale',
|
||||||
|
components: {
|
||||||
|
default: POS,
|
||||||
|
edit: QuickEditForm,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
default: true,
|
||||||
|
edit: (route) => route.query,
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({ routes, history: createWebHistory() });
|
const router = createRouter({ routes, history: createWebHistory() });
|
||||||
|
@ -5,20 +5,21 @@ import { systemLanguageRef } from './refs';
|
|||||||
|
|
||||||
// Language: Language Code in books/translations
|
// Language: Language Code in books/translations
|
||||||
export const languageCodeMap: Record<string, string> = {
|
export const languageCodeMap: Record<string, string> = {
|
||||||
|
Arabic: 'ar',
|
||||||
|
Catalan: 'ca-ES',
|
||||||
|
Danish: 'da',
|
||||||
|
Dutch: 'nl',
|
||||||
English: 'en',
|
English: 'en',
|
||||||
French: 'fr',
|
French: 'fr',
|
||||||
German: 'de',
|
German: 'de',
|
||||||
Portuguese: 'pt',
|
|
||||||
Arabic: 'ar',
|
|
||||||
Catalan: 'ca-ES',
|
|
||||||
Spanish: 'es',
|
|
||||||
Dutch: 'nl',
|
|
||||||
Gujarati: 'gu',
|
Gujarati: 'gu',
|
||||||
Turkish: 'tr',
|
|
||||||
Korean: 'ko',
|
Korean: 'ko',
|
||||||
Swedish: 'sv',
|
Nepali: 'np',
|
||||||
Danish: 'da',
|
Portuguese: 'pt',
|
||||||
'Simplified Chinese': 'zh-CN',
|
'Simplified Chinese': 'zh-CN',
|
||||||
|
Spanish: 'es',
|
||||||
|
Swedish: 'sv',
|
||||||
|
Turkish: 'tr',
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function setLanguageMap(
|
export async function setLanguageMap(
|
||||||
|
294
src/utils/pos.ts
Normal file
294
src/utils/pos.ts
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
import { Fyo, t } from 'fyo';
|
||||||
|
import { ValidationError } from 'fyo/utils/errors';
|
||||||
|
import { AccountTypeEnum } from 'models/baseModels/Account/types';
|
||||||
|
import { Item } from 'models/baseModels/Item/Item';
|
||||||
|
import { SalesInvoice } from 'models/baseModels/SalesInvoice/SalesInvoice';
|
||||||
|
import { SalesInvoiceItem } from 'models/baseModels/SalesInvoiceItem/SalesInvoiceItem';
|
||||||
|
import { POSShift } from 'models/inventory/Point of Sale/POSShift';
|
||||||
|
import { ValuationMethod } from 'models/inventory/types';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { Money } from 'pesa';
|
||||||
|
import {
|
||||||
|
getRawStockLedgerEntries,
|
||||||
|
getStockBalanceEntries,
|
||||||
|
getStockLedgerEntries,
|
||||||
|
} from 'reports/inventory/helpers';
|
||||||
|
import { ItemQtyMap, ItemSerialNumbers } from 'src/components/POS/types';
|
||||||
|
import { fyo } from 'src/initFyo';
|
||||||
|
import { safeParseFloat } from 'utils/index';
|
||||||
|
import { showToast } from './interactive';
|
||||||
|
|
||||||
|
export async function getItemQtyMap(): Promise<ItemQtyMap> {
|
||||||
|
const itemQtyMap: ItemQtyMap = {};
|
||||||
|
const valuationMethod =
|
||||||
|
fyo.singles.InventorySettings?.valuationMethod ?? ValuationMethod.FIFO;
|
||||||
|
|
||||||
|
const rawSLEs = await getRawStockLedgerEntries(fyo);
|
||||||
|
const rawData = getStockLedgerEntries(rawSLEs, valuationMethod);
|
||||||
|
const posInventory = fyo.singles.POSSettings?.inventory;
|
||||||
|
|
||||||
|
const stockBalance = getStockBalanceEntries(rawData, {
|
||||||
|
location: posInventory,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const row of stockBalance) {
|
||||||
|
if (!itemQtyMap[row.item]) {
|
||||||
|
itemQtyMap[row.item] = { availableQty: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.batch) {
|
||||||
|
itemQtyMap[row.item][row.batch] = row.balanceQuantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
itemQtyMap[row.item].availableQty += row.balanceQuantity;
|
||||||
|
}
|
||||||
|
return itemQtyMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTotalQuantity(items: SalesInvoiceItem[]): number {
|
||||||
|
let totalQuantity = safeParseFloat(0);
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
return totalQuantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const quantity = item.quantity ?? 0;
|
||||||
|
totalQuantity = safeParseFloat(totalQuantity + quantity);
|
||||||
|
}
|
||||||
|
return totalQuantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getItemDiscounts(items: SalesInvoiceItem[]): Money {
|
||||||
|
let itemDiscounts = fyo.pesa(0);
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
return itemDiscounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
if (!item.itemDiscountAmount?.isZero()) {
|
||||||
|
itemDiscounts = itemDiscounts.add(item.itemDiscountAmount as Money);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.amount && (item.itemDiscountPercent as number) > 1) {
|
||||||
|
itemDiscounts = itemDiscounts.add(
|
||||||
|
item.amount.percent(item.itemDiscountPercent as number)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return itemDiscounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getItem(item: string): Promise<Item | undefined> {
|
||||||
|
const itemDoc = (await fyo.doc.getDoc(ModelNameEnum.Item, item)) as Item;
|
||||||
|
if (!itemDoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateSinv(sinvDoc: SalesInvoice, itemQtyMap: ItemQtyMap) {
|
||||||
|
if (!sinvDoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateSinvItems(sinvDoc.items as SalesInvoiceItem[], itemQtyMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSinvItems(
|
||||||
|
sinvItems: SalesInvoiceItem[],
|
||||||
|
itemQtyMap: ItemQtyMap
|
||||||
|
) {
|
||||||
|
for (const item of sinvItems) {
|
||||||
|
if (!item.quantity || item.quantity < 1) {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`Invalid Quantity for Item ${item.item as string}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!itemQtyMap[item.item as string]) {
|
||||||
|
throw new ValidationError(t`Item ${item.item as string} not in Stock`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.quantity > itemQtyMap[item.item as string].availableQty) {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`Insufficient Quantity. Item ${item.item as string} has only ${
|
||||||
|
itemQtyMap[item.item as string].availableQty
|
||||||
|
} quantities available. you selected ${item.quantity}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateShipment(itemSerialNumbers: ItemSerialNumbers) {
|
||||||
|
if (!itemSerialNumbers) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const idx in itemSerialNumbers) {
|
||||||
|
const serialNumbers = itemSerialNumbers[idx].split('\n');
|
||||||
|
|
||||||
|
for (const serialNumber of serialNumbers) {
|
||||||
|
const status = await fyo.getValue(
|
||||||
|
ModelNameEnum.SerialNumber,
|
||||||
|
serialNumber,
|
||||||
|
'status'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (status !== 'Active') {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`Serial Number ${serialNumber} status is not Active.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateIsPosSettingsSet(fyo: Fyo) {
|
||||||
|
try {
|
||||||
|
const inventory = fyo.singles.POSSettings?.inventory;
|
||||||
|
if (!inventory) {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`POS Inventory is not set. Please set it on POS Settings`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cashAccount = fyo.singles.POSSettings?.cashAccount;
|
||||||
|
if (!cashAccount) {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`POS Counter Cash Account is not set. Please set it on POS Settings`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeOffAccount = fyo.singles.POSSettings?.writeOffAccount;
|
||||||
|
if (!writeOffAccount) {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`POS Write Off Account is not set. Please set it on POS Settings`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showToast({
|
||||||
|
type: 'error',
|
||||||
|
message: t`${error as string}`,
|
||||||
|
duration: 'long',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTotalTaxedAmount(sinvDoc: SalesInvoice): Money {
|
||||||
|
let totalTaxedAmount = fyo.pesa(0);
|
||||||
|
if (!sinvDoc.items?.length || !sinvDoc.taxes?.length) {
|
||||||
|
return totalTaxedAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const row of sinvDoc.taxes) {
|
||||||
|
totalTaxedAmount = totalTaxedAmount.add(row.amount as Money);
|
||||||
|
}
|
||||||
|
return totalTaxedAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateClosingAmounts(posShiftDoc: POSShift) {
|
||||||
|
try {
|
||||||
|
if (!posShiftDoc) {
|
||||||
|
throw new ValidationError(
|
||||||
|
`POS Shift Document not loaded. Please reload.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
posShiftDoc.closingAmounts?.map((row) => {
|
||||||
|
if (row.closingAmount?.isNegative()) {
|
||||||
|
throw new ValidationError(
|
||||||
|
t`Closing ${row.paymentMethod as string} Amount can not be negative.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function transferPOSCashAndWriteOff(
|
||||||
|
fyo: Fyo,
|
||||||
|
posShiftDoc: POSShift
|
||||||
|
) {
|
||||||
|
const expectedCashAmount = posShiftDoc.closingAmounts?.find(
|
||||||
|
(row) => row.paymentMethod === 'Cash'
|
||||||
|
)?.expectedAmount as Money;
|
||||||
|
|
||||||
|
if (expectedCashAmount.isZero()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const closingCashAmount = posShiftDoc.closingAmounts?.find(
|
||||||
|
(row) => row.paymentMethod === 'Cash'
|
||||||
|
)?.closingAmount as Money;
|
||||||
|
|
||||||
|
const jvDoc = fyo.doc.getNewDoc(ModelNameEnum.JournalEntry, {
|
||||||
|
entryType: 'Journal Entry',
|
||||||
|
});
|
||||||
|
|
||||||
|
await jvDoc.append('accounts', {
|
||||||
|
account: AccountTypeEnum.Cash,
|
||||||
|
debit: closingCashAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
await jvDoc.append('accounts', {
|
||||||
|
account: fyo.singles.POSSettings?.cashAccount,
|
||||||
|
credit: closingCashAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
const differenceAmount = posShiftDoc?.closingAmounts?.find(
|
||||||
|
(row) => row.paymentMethod === 'Cash'
|
||||||
|
)?.differenceAmount as Money;
|
||||||
|
|
||||||
|
if (differenceAmount.isNegative()) {
|
||||||
|
await jvDoc.append('accounts', {
|
||||||
|
account: AccountTypeEnum.Cash,
|
||||||
|
debit: differenceAmount.abs(),
|
||||||
|
credit: fyo.pesa(0),
|
||||||
|
});
|
||||||
|
await jvDoc.append('accounts', {
|
||||||
|
account: fyo.singles.POSSettings?.writeOffAccount,
|
||||||
|
debit: fyo.pesa(0),
|
||||||
|
credit: differenceAmount.abs(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!differenceAmount.isZero() && differenceAmount.isPositive()) {
|
||||||
|
await jvDoc.append('accounts', {
|
||||||
|
account: fyo.singles.POSSettings?.writeOffAccount,
|
||||||
|
debit: differenceAmount,
|
||||||
|
credit: fyo.pesa(0),
|
||||||
|
});
|
||||||
|
await jvDoc.append('accounts', {
|
||||||
|
account: AccountTypeEnum.Cash,
|
||||||
|
debit: fyo.pesa(0),
|
||||||
|
credit: differenceAmount,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await (await jvDoc.sync()).submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateSerialNumberCount(
|
||||||
|
serialNumbers: string | undefined,
|
||||||
|
quantity: number,
|
||||||
|
item: string
|
||||||
|
) {
|
||||||
|
let serialNumberCount = 0;
|
||||||
|
|
||||||
|
if (serialNumbers) {
|
||||||
|
serialNumberCount = serialNumbers.split('\n').length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quantity !== serialNumberCount) {
|
||||||
|
const errorMessage = t`Need ${quantity} Serial Numbers for Item ${item}. You have provided ${serialNumberCount}`;
|
||||||
|
|
||||||
|
showToast({
|
||||||
|
type: 'error',
|
||||||
|
message: errorMessage,
|
||||||
|
duration: 'long',
|
||||||
|
});
|
||||||
|
throw new ValidationError(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ const printSettingsFields = [
|
|||||||
'address',
|
'address',
|
||||||
'companyName',
|
'companyName',
|
||||||
];
|
];
|
||||||
const accountingSettingsFields = ['gstin'];
|
const accountingSettingsFields = ['gstin', 'taxId'];
|
||||||
|
|
||||||
export async function getPrintTemplatePropValues(
|
export async function getPrintTemplatePropValues(
|
||||||
doc: Doc
|
doc: Doc
|
||||||
@ -37,8 +37,6 @@ export async function getPrintTemplatePropValues(
|
|||||||
const fyo = doc.fyo;
|
const fyo = doc.fyo;
|
||||||
const values: PrintValues = { doc: {}, print: {} };
|
const values: PrintValues = { doc: {}, print: {} };
|
||||||
values.doc = await getPrintTemplateDocValues(doc);
|
values.doc = await getPrintTemplateDocValues(doc);
|
||||||
(values.doc as PrintTemplateData).entryType = doc.schema.name;
|
|
||||||
(values.doc as PrintTemplateData).entryLabel = doc.schema.label;
|
|
||||||
|
|
||||||
const printSettings = await fyo.doc.getDoc(ModelNameEnum.PrintSettings);
|
const printSettings = await fyo.doc.getDoc(ModelNameEnum.PrintSettings);
|
||||||
const printValues = await getPrintTemplateDocValues(
|
const printValues = await getPrintTemplateDocValues(
|
||||||
@ -72,8 +70,6 @@ export function getPrintTemplatePropHints(schemaName: string, fyo: Fyo) {
|
|||||||
const hints: PrintTemplateHint = {};
|
const hints: PrintTemplateHint = {};
|
||||||
const schema = fyo.schemaMap[schemaName]!;
|
const schema = fyo.schemaMap[schemaName]!;
|
||||||
hints.doc = getPrintTemplateDocHints(schema, fyo);
|
hints.doc = getPrintTemplateDocHints(schema, fyo);
|
||||||
hints.doc.entryType = fyo.t`Entry Type`;
|
|
||||||
hints.doc.entryLabel = fyo.t`Entry Label`;
|
|
||||||
|
|
||||||
const printSettingsHints = getPrintTemplateDocHints(
|
const printSettingsHints = getPrintTemplateDocHints(
|
||||||
fyo.schemaMap[ModelNameEnum.PrintSettings]!,
|
fyo.schemaMap[ModelNameEnum.PrintSettings]!,
|
||||||
@ -159,6 +155,10 @@ function getPrintTemplateDocHints(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hints.submitted = fyo.t`Submitted`;
|
||||||
|
hints.entryType = fyo.t`Entry Type`;
|
||||||
|
hints.entryLabel = fyo.t`Entry Label`;
|
||||||
|
|
||||||
if (Object.keys(links).length) {
|
if (Object.keys(links).length) {
|
||||||
hints.links = links;
|
hints.links = links;
|
||||||
}
|
}
|
||||||
@ -204,6 +204,10 @@ async function getPrintTemplateDocValues(doc: Doc, fieldnames?: string[]) {
|
|||||||
values[fieldname] = table;
|
values[fieldname] = table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
values.submitted = doc.submitted;
|
||||||
|
values.entryType = doc.schema.name;
|
||||||
|
values.entryLabel = doc.schema.label;
|
||||||
|
|
||||||
// Set Formatted Doc Link Data
|
// Set Formatted Doc Link Data
|
||||||
await doc.loadLinks();
|
await doc.loadLinks();
|
||||||
const links: PrintTemplateData = {};
|
const links: PrintTemplateData = {};
|
||||||
@ -347,6 +351,7 @@ function getNameAndTypeFromTemplateFile(
|
|||||||
* If the SchemaName is absent then it is assumed
|
* If the SchemaName is absent then it is assumed
|
||||||
* that the SchemaName is:
|
* that the SchemaName is:
|
||||||
* - SalesInvoice
|
* - SalesInvoice
|
||||||
|
* - SalesQuote
|
||||||
* - PurchaseInvoice
|
* - PurchaseInvoice
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -359,12 +364,14 @@ function getNameAndTypeFromTemplateFile(
|
|||||||
return [{ name: `${name} - ${label}`, type: schemaName }];
|
return [{ name: `${name} - ${label}`, type: schemaName }];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [ModelNameEnum.SalesInvoice, ModelNameEnum.PurchaseInvoice].map(
|
return [
|
||||||
(schemaName) => {
|
ModelNameEnum.SalesInvoice,
|
||||||
|
ModelNameEnum.SalesQuote,
|
||||||
|
ModelNameEnum.PurchaseInvoice,
|
||||||
|
].map((schemaName) => {
|
||||||
const label = fyo.schemaMap[schemaName]?.label ?? schemaName;
|
const label = fyo.schemaMap[schemaName]?.label ?? schemaName;
|
||||||
return { name: `${name} - ${label}`, type: schemaName };
|
return { name: `${name} - ${label}`, type: schemaName };
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const baseTemplate = `<main class="h-full w-full bg-white">
|
export const baseTemplate = `<main class="h-full w-full bg-white">
|
||||||
|
@ -101,6 +101,20 @@ function getInventorySidebar(): SidebarRoot[] {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPOSSidebar() {
|
||||||
|
const isPOSEnabled = !!fyo.singles.InventorySettings?.enablePointOfSale;
|
||||||
|
if (!isPOSEnabled) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: t`POS`,
|
||||||
|
name: 'pos',
|
||||||
|
route: '/pos',
|
||||||
|
icon: 'pos',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getReportSidebar() {
|
function getReportSidebar() {
|
||||||
return {
|
return {
|
||||||
label: t`Reports`,
|
label: t`Reports`,
|
||||||
@ -155,6 +169,12 @@ function getCompleteSidebar(): SidebarConfig {
|
|||||||
icon: 'sales',
|
icon: 'sales',
|
||||||
route: '/list/SalesInvoice',
|
route: '/list/SalesInvoice',
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
label: t`Sales Quotes`,
|
||||||
|
name: 'sales-quotes',
|
||||||
|
route: '/list/SalesQuote',
|
||||||
|
schemaName: 'SalesQuote',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t`Sales Invoices`,
|
label: t`Sales Invoices`,
|
||||||
name: 'sales-invoices',
|
name: 'sales-invoices',
|
||||||
@ -256,6 +276,7 @@ function getCompleteSidebar(): SidebarConfig {
|
|||||||
},
|
},
|
||||||
getReportSidebar(),
|
getReportSidebar(),
|
||||||
getInventorySidebar(),
|
getInventorySidebar(),
|
||||||
|
getPOSSidebar(),
|
||||||
getRegionalSidebar(),
|
getRegionalSidebar(),
|
||||||
{
|
{
|
||||||
label: t`Setup`,
|
label: t`Setup`,
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
|
${0},,
|
||||||
"${0} ${1} already exists.","${0} ${1} موجودة من قبل.",
|
"${0} ${1} already exists.","${0} ${1} موجودة من قبل.",
|
||||||
"${0} ${1} does not exist",,
|
"${0} ${1} does not exist","${0} ${1} غير موجود",
|
||||||
"${0} ${1} has been modified after loading please reload entry.",,
|
"${0} ${1} has been modified after loading please reload entry.","${0} ${1} تم تعديله بعد التحميل يرجى إعادة تحميل الإدخال.",
|
||||||
"${0} ${1} is linked with existing records.","${0} ${1} مرطبت بسجل موجود.",
|
"${0} ${1} is linked with existing records.","${0} ${1} مرطبت بسجل موجود.",
|
||||||
"${0} account not set in Inventory Settings.",,
|
"${0} account not set in Inventory Settings.","${0} لم يتم تعيين الحساب في إعدادات المخزون.",
|
||||||
"${0} already saved",,
|
"${0} already saved","${0} تم حفظ بالفعل",
|
||||||
"${0} already submitted",,
|
"${0} already submitted","${0} تم الارسال بالفعل",
|
||||||
"${0} cancelled",,
|
"${0} cancelled","${0} تم الالغاء",
|
||||||
"${0} cannot be cancelled",,
|
"${0} cannot be cancelled","${0} لا يمكن الالغاء",
|
||||||
"${0} cannot be deleted",,
|
"${0} cannot be deleted","${0} لا يمكن الحذف",
|
||||||
"${0} deleted",,
|
"${0} deleted","${0} تم الحذف",
|
||||||
"${0} entries failed",,
|
"${0} entries failed","${0} فشل الادخال",
|
||||||
"${0} entries imported",,
|
"${0} entries imported","${0} الإدخالات المستوردة",
|
||||||
"${0} entry failed",,
|
"${0} entry failed","${0} فشل الإدخال",
|
||||||
"${0} entry imported",,
|
"${0} entry imported","${0} الإدخال مستورد",
|
||||||
"${0} fields selected",,
|
"${0} fields selected","${0} الحقول المحددة",
|
||||||
"${0} filters applied","${0} تم تطبيق الفيلتر",
|
"${0} filters applied","${0} تم تطبيق الفيلتر",
|
||||||
"${0} has linked child accounts.",,
|
"${0} has linked child accounts.","${0} لديه حسابات فرعية مرتبطة.",
|
||||||
"${0} of type ${1} does not exist",,
|
"${0} of type ${1} does not exist","${0} من النوع ${1} غير موجود",
|
||||||
"${0} out of ${1}",,
|
"${0} out of ${1}",,
|
||||||
"${0} party ${1} is different from ${2}",,
|
"${0} party ${1} is different from ${2}",,
|
||||||
"${0} quantity 1 added.",,
|
"${0} quantity 1 added.",,
|
||||||
@ -70,6 +71,7 @@ Active,,
|
|||||||
"Add products or services that you buy from your suppliers",,
|
"Add products or services that you buy from your suppliers",,
|
||||||
"Add products or services that you sell to your customers",,
|
"Add products or services that you sell to your customers",,
|
||||||
"Add transfer terms",,
|
"Add transfer terms",,
|
||||||
|
"Add'l Discounts",,
|
||||||
"Additional ${0} Serial Numbers required for ${1} quantity of ${2}.",,
|
"Additional ${0} Serial Numbers required for ${1} quantity of ${2}.",,
|
||||||
"Additional quantity (${0}) required${1} to make outward transfer of item ${2} from ${3} on ${4}",,
|
"Additional quantity (${0}) required${1} to make outward transfer of item ${2} from ${3} on ${4}",,
|
||||||
Address,العنوان,
|
Address,العنوان,
|
||||||
@ -105,9 +107,11 @@ August,,
|
|||||||
"Auto Payments"," خاصية الدفع التلقائي",
|
"Auto Payments"," خاصية الدفع التلقائي",
|
||||||
"Auto Stock Transfer"," خاصية نقل المنتجات تلقائيًا بين المخازن",
|
"Auto Stock Transfer"," خاصية نقل المنتجات تلقائيًا بين المخازن",
|
||||||
Autocomplete,,
|
Autocomplete,,
|
||||||
|
Back,,
|
||||||
"Back Reference",,
|
"Back Reference",,
|
||||||
"Bad import data, could not read file.",,
|
"Bad import data, could not read file.",,
|
||||||
Balance,التوازن,
|
Balance,التوازن,
|
||||||
|
"Balance Amount",,
|
||||||
"Balance Sheet","تقرير الميزانية",
|
"Balance Sheet","تقرير الميزانية",
|
||||||
Bank,,
|
Bank,,
|
||||||
"Bank Accounts","حسابات بنكية",
|
"Bank Accounts","حسابات بنكية",
|
||||||
@ -138,6 +142,7 @@ Cancelled,ملغاة,
|
|||||||
"Cannot Import",,
|
"Cannot Import",,
|
||||||
"Cannot Open File",,
|
"Cannot Open File",,
|
||||||
"Cannot cancel ${0} ${1} because of the following ${2}: ${3}",,
|
"Cannot cancel ${0} ${1} because of the following ${2}: ${3}",,
|
||||||
|
"Cannot cancel ${0} because of the following ${1}: ${2}",,
|
||||||
"Cannot delete ${0} ""${1}"" because of linked entries.",,
|
"Cannot delete ${0} ""${1}"" because of linked entries.",,
|
||||||
"Cannot open file",,
|
"Cannot open file",,
|
||||||
"Cannot perform operation.",,
|
"Cannot perform operation.",,
|
||||||
@ -145,6 +150,7 @@ Cancelled,ملغاة,
|
|||||||
"Capital Equipments","معدات رأس المال",
|
"Capital Equipments","معدات رأس المال",
|
||||||
"Capital Stock","رأس المال",
|
"Capital Stock","رأس المال",
|
||||||
Cash,نقد,
|
Cash,نقد,
|
||||||
|
"Cash Denominations",,
|
||||||
"Cash Entry","قيود النقدية",
|
"Cash Entry","قيود النقدية",
|
||||||
"Cash In Hand","نقدا في اليد",
|
"Cash In Hand","نقدا في اليد",
|
||||||
Cashflow,"التدفق النقدي",
|
Cashflow,"التدفق النقدي",
|
||||||
@ -163,10 +169,15 @@ Clear,واضح,
|
|||||||
"Clearance Date","تاريخ التخليص",
|
"Clearance Date","تاريخ التخليص",
|
||||||
Close,إغلاق,
|
Close,إغلاق,
|
||||||
"Close Frappe Books and try manually.",,
|
"Close Frappe Books and try manually.",,
|
||||||
|
"Close POS Shift",,
|
||||||
"Close Quick Search",,
|
"Close Quick Search",,
|
||||||
Closing,اغلاق,
|
Closing,اغلاق,
|
||||||
|
"Closing ${0} Amount can not be negative.",,
|
||||||
"Closing (Cr)","(Cr) إغلاق",
|
"Closing (Cr)","(Cr) إغلاق",
|
||||||
"Closing (Dr)","(Dr) إغلاق",
|
"Closing (Dr)","(Dr) إغلاق",
|
||||||
|
"Closing Amount",,
|
||||||
|
"Closing Cash In Denominations",,
|
||||||
|
"Closing Date",,
|
||||||
Collapse,,
|
Collapse,,
|
||||||
Color,اللون,
|
Color,اللون,
|
||||||
"Commission on Sales","عمولة على المبيعات",
|
"Commission on Sales","عمولة على المبيعات",
|
||||||
@ -187,6 +198,8 @@ Contains,يحتوي,
|
|||||||
"Cost Of Goods Sold Acc."," حساب تكلفة البضاعة المباعة ",
|
"Cost Of Goods Sold Acc."," حساب تكلفة البضاعة المباعة ",
|
||||||
"Cost of Goods Sold","تكلفة البضائع المباعة",
|
"Cost of Goods Sold","تكلفة البضائع المباعة",
|
||||||
"Could not connect to database file ${0}, please select the file manually",,
|
"Could not connect to database file ${0}, please select the file manually",,
|
||||||
|
Count,,
|
||||||
|
"Counter Cash Account",,
|
||||||
Country,البلد,
|
Country,البلد,
|
||||||
"Country Code","كود البلد",
|
"Country Code","كود البلد",
|
||||||
"Country code used to initialize regional settings.","كود البلد يستخدم لتثبت اعدادات الاقليمية",
|
"Country code used to initialize regional settings.","كود البلد يستخدم لتثبت اعدادات الاقليمية",
|
||||||
@ -246,6 +259,7 @@ December,,
|
|||||||
"Decrease print template display scale",,
|
"Decrease print template display scale",,
|
||||||
Default,,
|
Default,,
|
||||||
"Default Account","الحساب الافتراضي",
|
"Default Account","الحساب الافتراضي",
|
||||||
|
"Default Cash Denominations",,
|
||||||
"Default Location"," الموقع الافتراضي",
|
"Default Location"," الموقع الافتراضي",
|
||||||
Defaults,"الاعدادات الافتراضية ",
|
Defaults,"الاعدادات الافتراضية ",
|
||||||
Delete,حذف,
|
Delete,حذف,
|
||||||
@ -254,10 +268,12 @@ Delete,حذف,
|
|||||||
"Delete Failed",,
|
"Delete Failed",,
|
||||||
"Delete Group",,
|
"Delete Group",,
|
||||||
Delivered,,
|
Delivered,,
|
||||||
|
Denomination,,
|
||||||
Depreciation,إهلاك,
|
Depreciation,إهلاك,
|
||||||
"Depreciation Entry","ادخال قيد الاهلاك",
|
"Depreciation Entry","ادخال قيد الاهلاك",
|
||||||
Description,الوصف,
|
Description,الوصف,
|
||||||
Details,التفاصيل,
|
Details,التفاصيل,
|
||||||
|
"Difference Amount",,
|
||||||
"Direct Expenses","المصاريف المباشرة",
|
"Direct Expenses","المصاريف المباشرة",
|
||||||
"Direct Income","الدخل المباشر",
|
"Direct Income","الدخل المباشر",
|
||||||
"Directory for database file ${0} does not exist, please select the file manually",,
|
"Directory for database file ${0} does not exist, please select the file manually",,
|
||||||
@ -301,6 +317,8 @@ Empty,فارغ,
|
|||||||
"Enable Discount Accounting"," تفعيل حساب الخصم",
|
"Enable Discount Accounting"," تفعيل حساب الخصم",
|
||||||
"Enable Form Customization",,
|
"Enable Form Customization",,
|
||||||
"Enable Inventory"," تفعيل المخزون",
|
"Enable Inventory"," تفعيل المخزون",
|
||||||
|
"Enable Invoice Returns",,
|
||||||
|
"Enable Point of Sale",,
|
||||||
"Enable Price List"," تفعيل قائمة الاسعار",
|
"Enable Price List"," تفعيل قائمة الاسعار",
|
||||||
"Enable Serial Number"," تفعيل السريال نمبر",
|
"Enable Serial Number"," تفعيل السريال نمبر",
|
||||||
"Enable Stock Returns",,
|
"Enable Stock Returns",,
|
||||||
@ -325,6 +343,7 @@ Error,خطأ,
|
|||||||
"Excise Entry",,
|
"Excise Entry",,
|
||||||
"Existing Company",,
|
"Existing Company",,
|
||||||
Expand,,
|
Expand,,
|
||||||
|
"Expected Amount",,
|
||||||
Expense,المصاريف,
|
Expense,المصاريف,
|
||||||
"Expense Account","حساب المصروف",
|
"Expense Account","حساب المصروف",
|
||||||
Expenses,المصروفات,
|
Expenses,المصروفات,
|
||||||
@ -335,7 +354,6 @@ Export,تصدير,
|
|||||||
"Export Format",,
|
"Export Format",,
|
||||||
"Export Successful","تم التصدير بنجاح",
|
"Export Successful","تم التصدير بنجاح",
|
||||||
"Export Wizard",,
|
"Export Wizard",,
|
||||||
FIFO,,
|
|
||||||
Failed,,
|
Failed,,
|
||||||
Fax,فاكس,
|
Fax,فاكس,
|
||||||
Features," الخصائص",
|
Features," الخصائص",
|
||||||
@ -427,10 +445,12 @@ Inflow,التدفق,
|
|||||||
"Instance Id",,
|
"Instance Id",,
|
||||||
"Insufficient Quantity",,
|
"Insufficient Quantity",,
|
||||||
"Insufficient Quantity.",,
|
"Insufficient Quantity.",,
|
||||||
|
"Insufficient Quantity. Item ${0} has only ${1} quantities available. you selected ${2}",,
|
||||||
Int,,
|
Int,,
|
||||||
"Intergrated Tax","ضريبة متكاملة",
|
"Intergrated Tax","ضريبة متكاملة",
|
||||||
"Internal Precision","الدقة الداخلية",
|
"Internal Precision","الدقة الداخلية",
|
||||||
"Invalid Key Error",,
|
"Invalid Key Error",,
|
||||||
|
"Invalid Quantity for Item ${0}",,
|
||||||
"Invalid barcode value ${0}.",,
|
"Invalid barcode value ${0}.",,
|
||||||
"Invalid value ${0} for ${1}",,
|
"Invalid value ${0} for ${1}",,
|
||||||
"Invalid value found for ${0}",,
|
"Invalid value found for ${0}",,
|
||||||
@ -452,13 +472,17 @@ Is,هو,
|
|||||||
"Is Landscape",,
|
"Is Landscape",,
|
||||||
"Is Not",ليس,
|
"Is Not",ليس,
|
||||||
"Is Not Empty","ليس فارغ",
|
"Is Not Empty","ليس فارغ",
|
||||||
|
"Is POS Shift Open",,
|
||||||
"Is Price List Enabled",,
|
"Is Price List Enabled",,
|
||||||
"Is Required",,
|
"Is Required",,
|
||||||
"Is Whole",,
|
"Is Whole",,
|
||||||
Item,صنف,
|
Item,صنف,
|
||||||
|
"Item ${0} has Zero Quantity",,
|
||||||
"Item ${0} is a batched item",,
|
"Item ${0} is a batched item",,
|
||||||
"Item ${0} is not a batched item",,
|
"Item ${0} is not a batched item",,
|
||||||
|
"Item ${0} not in Stock",,
|
||||||
"Item Description",,
|
"Item Description",,
|
||||||
|
"Item Discounts",,
|
||||||
"Item Name","اسم العنصر",
|
"Item Name","اسم العنصر",
|
||||||
"Item Prices",,
|
"Item Prices",,
|
||||||
"Item with From location not found",,
|
"Item with From location not found",,
|
||||||
@ -527,9 +551,9 @@ More,,
|
|||||||
"More Filters",,
|
"More Filters",,
|
||||||
"More shortcuts will be added soon.",,
|
"More shortcuts will be added soon.",,
|
||||||
"Movement Type"," نوع التنقل ",
|
"Movement Type"," نوع التنقل ",
|
||||||
"Moving Average",,
|
|
||||||
Name,الاسم,
|
Name,الاسم,
|
||||||
Navigate,,
|
Navigate,,
|
||||||
|
"Need ${0} Serial Numbers for Item ${1}. You have provided ${2}",,
|
||||||
"Net Total","الإجمالي الصافي",
|
"Net Total","الإجمالي الصافي",
|
||||||
"New ${0}",,
|
"New ${0}",,
|
||||||
"New ${0} ${1}",,
|
"New ${0} ${1}",,
|
||||||
@ -582,8 +606,12 @@ Okay,,
|
|||||||
"Open the Export Wizard modal",,
|
"Open the Export Wizard modal",,
|
||||||
"Opening (Cr)","(Cr) الافتتاح",
|
"Opening (Cr)","(Cr) الافتتاح",
|
||||||
"Opening (Dr)","(Dr) افتتاح",
|
"Opening (Dr)","(Dr) افتتاح",
|
||||||
|
"Opening Amount",,
|
||||||
"Opening Balance Equity","الرصيد الافتتاحي حقوق الملكية",
|
"Opening Balance Equity","الرصيد الافتتاحي حقوق الملكية",
|
||||||
"Opening Balances","أرصدة الافتتاح",
|
"Opening Balances","أرصدة الافتتاح",
|
||||||
|
"Opening Cash Amount can not be negative.",,
|
||||||
|
"Opening Cash In Denominations",,
|
||||||
|
"Opening Date",,
|
||||||
"Opening Entry",,
|
"Opening Entry",,
|
||||||
Options,,
|
Options,,
|
||||||
Orange,,
|
Orange,,
|
||||||
@ -591,10 +619,18 @@ Organisation,التنظيم,
|
|||||||
Outflow,التدفق,
|
Outflow,التدفق,
|
||||||
Outstanding,,
|
Outstanding,,
|
||||||
"Outstanding Amount","المبلغ المستحق",
|
"Outstanding Amount","المبلغ المستحق",
|
||||||
|
POS,,
|
||||||
|
"POS Counter Cash Account is not set. Please set it on POS Settings",,
|
||||||
|
"POS Customer",,
|
||||||
|
"POS Inventory is not set. Please set it on POS Settings",,
|
||||||
|
"POS Settings",,
|
||||||
|
"POS Shift Amount",,
|
||||||
|
"POS Write Off Account is not set. Please set it on POS Settings",,
|
||||||
"Pad Zeros",,
|
"Pad Zeros",,
|
||||||
Page,,
|
Page,,
|
||||||
Paid,مدفوع,
|
Paid,مدفوع,
|
||||||
"Paid ${0}",,
|
"Paid ${0}",,
|
||||||
|
"Paid Change",,
|
||||||
Parent,الوالد,
|
Parent,الوالد,
|
||||||
"Parent Account","الحساب الوالد",
|
"Parent Account","الحساب الوالد",
|
||||||
Party,"مورد / عميل",
|
Party,"مورد / عميل",
|
||||||
@ -602,6 +638,7 @@ Party,"مورد / عميل",
|
|||||||
Pay,دفع,
|
Pay,دفع,
|
||||||
Payable,,
|
Payable,,
|
||||||
Payment,الدفع,
|
Payment,الدفع,
|
||||||
|
"Payment ${0} is Saved",,
|
||||||
"Payment For","الدفع مقابل",
|
"Payment For","الدفع مقابل",
|
||||||
"Payment Method","طريقة الدفع",
|
"Payment Method","طريقة الدفع",
|
||||||
"Payment No","رقم الدفع",
|
"Payment No","رقم الدفع",
|
||||||
@ -637,6 +674,7 @@ Place,مكان,
|
|||||||
"Please set GSTIN in General Settings.",,
|
"Please set GSTIN in General Settings.",,
|
||||||
"Please set Round Off Account in the Settings.",,
|
"Please set Round Off Account in the Settings.",,
|
||||||
"Please set a Display Doc",,
|
"Please set a Display Doc",,
|
||||||
|
"Point of Sale",,
|
||||||
"Postal Code","الرمز البريدي",
|
"Postal Code","الرمز البريدي",
|
||||||
"Postal Expenses","المصاريف البريدية",
|
"Postal Expenses","المصاريف البريدية",
|
||||||
"Posting Date","تاريخ النشر",
|
"Posting Date","تاريخ النشر",
|
||||||
@ -680,6 +718,7 @@ Purchase,شراء,
|
|||||||
Purchases,المشتريات,
|
Purchases,المشتريات,
|
||||||
Purple,,
|
Purple,,
|
||||||
Purpose,,
|
Purpose,,
|
||||||
|
"Qty in Batch",,
|
||||||
"Qty. ${0}",,
|
"Qty. ${0}",,
|
||||||
"Qty. in Transfer Unit",,
|
"Qty. in Transfer Unit",,
|
||||||
Quantity,الكمية,
|
Quantity,الكمية,
|
||||||
@ -689,6 +728,8 @@ Quarterly,,
|
|||||||
Quarters,,
|
Quarters,,
|
||||||
"Quick Search",,
|
"Quick Search",,
|
||||||
"Quick edit error: ${0} entry has no name.",,
|
"Quick edit error: ${0} entry has no name.",,
|
||||||
|
Quote,,
|
||||||
|
"Quote Reference",,
|
||||||
Rate,معدل,
|
Rate,معدل,
|
||||||
"Rate (${0}) cannot be less zero.","لا يمكن أن يكون السعر (${0}) أقل من صفر.",
|
"Rate (${0}) cannot be less zero.","لا يمكن أن يكون السعر (${0}) أقل من صفر.",
|
||||||
"Rate (${0}) has to be greater than zero",,
|
"Rate (${0}) has to be greater than zero",,
|
||||||
@ -739,6 +780,7 @@ Sales,المبيعات,
|
|||||||
"Sales Acc."," حساب المبيعات",
|
"Sales Acc."," حساب المبيعات",
|
||||||
"Sales Expenses","مصاريف المبيعات",
|
"Sales Expenses","مصاريف المبيعات",
|
||||||
"Sales Invoice","فاتورة مبيعات",
|
"Sales Invoice","فاتورة مبيعات",
|
||||||
|
"Sales Invoice ${0} is Submitted",,
|
||||||
"Sales Invoice Item","اصناف فاتورة المبيعات",
|
"Sales Invoice Item","اصناف فاتورة المبيعات",
|
||||||
"Sales Invoice Number Series"," تسلسل فاتورة المبيعات",
|
"Sales Invoice Number Series"," تسلسل فاتورة المبيعات",
|
||||||
"Sales Invoice Print Template"," قالب الطابعة فاتورة المبيعات ",
|
"Sales Invoice Print Template"," قالب الطابعة فاتورة المبيعات ",
|
||||||
@ -750,6 +792,11 @@ Sales,المبيعات,
|
|||||||
"Sales Payment",,
|
"Sales Payment",,
|
||||||
"Sales Payment Account"," حساب دفع المبيعات",
|
"Sales Payment Account"," حساب دفع المبيعات",
|
||||||
"Sales Payments","مدفوعات المبيعات",
|
"Sales Payments","مدفوعات المبيعات",
|
||||||
|
"Sales Quote",,
|
||||||
|
"Sales Quote Item",,
|
||||||
|
"Sales Quote Number Series",,
|
||||||
|
"Sales Quote Print Template",,
|
||||||
|
"Sales Quotes",,
|
||||||
"Sales and Purchase",,
|
"Sales and Purchase",,
|
||||||
Save,حفظ,
|
Save,حفظ,
|
||||||
"Save ${0}?",,
|
"Save ${0}?",,
|
||||||
@ -761,6 +808,7 @@ Save,حفظ,
|
|||||||
"Save changes made to ${0}?",,
|
"Save changes made to ${0}?",,
|
||||||
"Save or Submit an entry.",,
|
"Save or Submit an entry.",,
|
||||||
Saved,,
|
Saved,,
|
||||||
|
"Search an Item",,
|
||||||
"Secured Loans","قروض مضمونة",
|
"Secured Loans","قروض مضمونة",
|
||||||
"Securities and Deposits","الأوراق المالية والودائع",
|
"Securities and Deposits","الأوراق المالية والودائع",
|
||||||
Select,تحديد,
|
Select,تحديد,
|
||||||
@ -777,6 +825,7 @@ Select,تحديد,
|
|||||||
"Select column",,
|
"Select column",,
|
||||||
"Select file","تحديد ملف",
|
"Select file","تحديد ملف",
|
||||||
"Select folder","تحديد مجلد",
|
"Select folder","تحديد مجلد",
|
||||||
|
"Select the template type.",,
|
||||||
Selected,,
|
Selected,,
|
||||||
September,,
|
September,,
|
||||||
"Serial Number",,
|
"Serial Number",,
|
||||||
@ -784,6 +833,7 @@ September,,
|
|||||||
"Serial Number ${0} does not exist.",,
|
"Serial Number ${0} does not exist.",,
|
||||||
"Serial Number ${0} is not Active.",,
|
"Serial Number ${0} is not Active.",,
|
||||||
"Serial Number ${0} is not Inactive",,
|
"Serial Number ${0} is not Inactive",,
|
||||||
|
"Serial Number ${0} status is not Active.",,
|
||||||
"Serial Number Description",,
|
"Serial Number Description",,
|
||||||
"Serial Number is enabled for Item ${0}",,
|
"Serial Number is enabled for Item ${0}",,
|
||||||
"Serial Number is not enabled for Item ${0}",,
|
"Serial Number is not enabled for Item ${0}",,
|
||||||
@ -793,6 +843,7 @@ Service,الخدمة,
|
|||||||
"Set Discount Amount",,
|
"Set Discount Amount",,
|
||||||
"Set Period",,
|
"Set Period",,
|
||||||
"Set Print Size",,
|
"Set Print Size",,
|
||||||
|
"Set Template Type",,
|
||||||
"Set Up",,
|
"Set Up",,
|
||||||
"Set Up Your Workspace","إعداد مساحة العمل الخاصة بك",
|
"Set Up Your Workspace","إعداد مساحة العمل الخاصة بك",
|
||||||
"Set a Template value to see the Print Template",,
|
"Set a Template value to see the Print Template",,
|
||||||
@ -813,6 +864,7 @@ Setup,الإعداد,
|
|||||||
"Setup Wizard","معالج الإعداد",
|
"Setup Wizard","معالج الإعداد",
|
||||||
"Setup system defaults like date format and display precision",,
|
"Setup system defaults like date format and display precision",,
|
||||||
Shipment," الشحن ",
|
Shipment," الشحن ",
|
||||||
|
"Shipment ${0} is Submitted",,
|
||||||
"Shipment Item",,
|
"Shipment Item",,
|
||||||
"Shipment Location"," موقع شحن البضاعة",
|
"Shipment Location"," موقع شحن البضاعة",
|
||||||
"Shipment Number Series"," تسلسل شحن المخزون",
|
"Shipment Number Series"," تسلسل شحن المخزون",
|
||||||
@ -865,6 +917,7 @@ Stores," المخازن",
|
|||||||
Su,,
|
Su,,
|
||||||
Submit,إرسال,
|
Submit,إرسال,
|
||||||
"Submit ${0}?",,
|
"Submit ${0}?",,
|
||||||
|
"Submit & Print",,
|
||||||
"Submit entries?",,
|
"Submit entries?",,
|
||||||
Submitted,مقدم,
|
Submitted,مقدم,
|
||||||
Success,,
|
Success,,
|
||||||
@ -883,6 +936,8 @@ Tax,الضريبة,
|
|||||||
"Tax Assets","الأصول الضريبية",
|
"Tax Assets","الأصول الضريبية",
|
||||||
"Tax Detail","التفاصيل الضريبية",
|
"Tax Detail","التفاصيل الضريبية",
|
||||||
"Tax ID","المعرّف الضريبي",
|
"Tax ID","المعرّف الضريبي",
|
||||||
|
"Tax Invoice Account",,
|
||||||
|
"Tax Payment Account",,
|
||||||
"Tax Rate",,
|
"Tax Rate",,
|
||||||
"Tax Summary","ملخص الضريبة",
|
"Tax Summary","ملخص الضريبة",
|
||||||
"Tax Template",,
|
"Tax Template",,
|
||||||
@ -891,6 +946,7 @@ Tax,الضريبة,
|
|||||||
"Taxable Value","القيمة الخاضعة للضريبة",
|
"Taxable Value","القيمة الخاضعة للضريبة",
|
||||||
"Taxed Amount",,
|
"Taxed Amount",,
|
||||||
Taxes,الضرائب,
|
Taxes,الضرائب,
|
||||||
|
"Taxes and Charges",,
|
||||||
Teal,,
|
Teal,,
|
||||||
"Telephone Expenses","مصاريف الهاتف",
|
"Telephone Expenses","مصاريف الهاتف",
|
||||||
Template,قالب,
|
Template,قالب,
|
||||||
@ -943,6 +999,7 @@ Total,اجمالى,
|
|||||||
"Total Income (Credit)","اجمالى الدخل(دائن)",
|
"Total Income (Credit)","اجمالى الدخل(دائن)",
|
||||||
"Total Liability (Credit)","اجمالى الالتزامات(دائن)",
|
"Total Liability (Credit)","اجمالى الالتزامات(دائن)",
|
||||||
"Total Profit","اجمالى الربح",
|
"Total Profit","اجمالى الربح",
|
||||||
|
"Total Quantity",,
|
||||||
"Total Spending","إجمالي الإنفاق",
|
"Total Spending","إجمالي الإنفاق",
|
||||||
"Track Inventory",,
|
"Track Inventory",,
|
||||||
Transfer,تحويل,
|
Transfer,تحويل,
|
||||||
@ -970,7 +1027,6 @@ Unpaid,"غير مدفوعة الأجر",
|
|||||||
"User Remark","ملاحظة المستخدم",
|
"User Remark","ملاحظة المستخدم",
|
||||||
"Utility Expenses","نفقات المرافق",
|
"Utility Expenses","نفقات المرافق",
|
||||||
"Validation Error",,
|
"Validation Error",,
|
||||||
"Valuation Method"," طريقة تقييم المخزون",
|
|
||||||
Value,القيمة,
|
Value,القيمة,
|
||||||
"Value missing for ${0}","القيمة مفقودة لـ ${0}",
|
"Value missing for ${0}","القيمة مفقودة لـ ${0}",
|
||||||
"Value: ${0}",,
|
"Value: ${0}",,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user