init
This commit is contained in:
commit
aa9ece09da
190 changed files with 4850 additions and 0 deletions
21
.editorconfig
Normal file
21
.editorconfig
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Root editor config file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# Common settings
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
charset = utf-8
|
||||||
|
|
||||||
|
# python, js indentation settings
|
||||||
|
[{*.py,*.js,*.vue,*.css,*.scss,*.html}]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
max_line_length = 99
|
||||||
|
|
||||||
|
# JSON files - mostly doctype schema files
|
||||||
|
[{*.json}]
|
||||||
|
insert_final_newline = false
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 1
|
||||||
124
.eslintrc
Normal file
124
.eslintrc
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"node": true,
|
||||||
|
"es2022": true
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"extends": "eslint:recommended",
|
||||||
|
"rules": {
|
||||||
|
"indent": "off",
|
||||||
|
"brace-style": "off",
|
||||||
|
"no-mixed-spaces-and-tabs": "off",
|
||||||
|
"no-useless-escape": "off",
|
||||||
|
"space-unary-ops": ["error", { "words": true }],
|
||||||
|
"linebreak-style": "off",
|
||||||
|
"quotes": ["off"],
|
||||||
|
"semi": "off",
|
||||||
|
"camelcase": "off",
|
||||||
|
"no-unused-vars": "off",
|
||||||
|
"no-console": ["warn"],
|
||||||
|
"no-extra-boolean-cast": ["off"],
|
||||||
|
"no-control-regex": ["off"],
|
||||||
|
},
|
||||||
|
"root": true,
|
||||||
|
"globals": {
|
||||||
|
"frappe": true,
|
||||||
|
"Vue": true,
|
||||||
|
"SetVueGlobals": true,
|
||||||
|
"__": true,
|
||||||
|
"repl": true,
|
||||||
|
"Class": true,
|
||||||
|
"locals": true,
|
||||||
|
"cint": true,
|
||||||
|
"cstr": true,
|
||||||
|
"cur_frm": true,
|
||||||
|
"cur_dialog": true,
|
||||||
|
"cur_page": true,
|
||||||
|
"cur_list": true,
|
||||||
|
"cur_tree": true,
|
||||||
|
"msg_dialog": true,
|
||||||
|
"is_null": true,
|
||||||
|
"in_list": true,
|
||||||
|
"has_common": true,
|
||||||
|
"posthog": true,
|
||||||
|
"has_words": true,
|
||||||
|
"validate_email": true,
|
||||||
|
"open_web_template_values_editor": true,
|
||||||
|
"validate_name": true,
|
||||||
|
"validate_phone": true,
|
||||||
|
"validate_url": true,
|
||||||
|
"get_number_format": true,
|
||||||
|
"format_number": true,
|
||||||
|
"format_currency": true,
|
||||||
|
"comment_when": true,
|
||||||
|
"open_url_post": true,
|
||||||
|
"toTitle": true,
|
||||||
|
"lstrip": true,
|
||||||
|
"rstrip": true,
|
||||||
|
"strip": true,
|
||||||
|
"strip_html": true,
|
||||||
|
"replace_all": true,
|
||||||
|
"flt": true,
|
||||||
|
"precision": true,
|
||||||
|
"CREATE": true,
|
||||||
|
"AMEND": true,
|
||||||
|
"CANCEL": true,
|
||||||
|
"copy_dict": true,
|
||||||
|
"get_number_format_info": true,
|
||||||
|
"strip_number_groups": true,
|
||||||
|
"print_table": true,
|
||||||
|
"Layout": true,
|
||||||
|
"web_form_settings": true,
|
||||||
|
"$c": true,
|
||||||
|
"$a": true,
|
||||||
|
"$i": true,
|
||||||
|
"$bg": true,
|
||||||
|
"$y": true,
|
||||||
|
"$c_obj": true,
|
||||||
|
"refresh_many": true,
|
||||||
|
"refresh_field": true,
|
||||||
|
"toggle_field": true,
|
||||||
|
"get_field_obj": true,
|
||||||
|
"get_query_params": true,
|
||||||
|
"unhide_field": true,
|
||||||
|
"hide_field": true,
|
||||||
|
"set_field_options": true,
|
||||||
|
"getCookie": true,
|
||||||
|
"getCookies": true,
|
||||||
|
"get_url_arg": true,
|
||||||
|
"md5": true,
|
||||||
|
"$": true,
|
||||||
|
"jQuery": true,
|
||||||
|
"moment": true,
|
||||||
|
"hljs": true,
|
||||||
|
"Awesomplete": true,
|
||||||
|
"Sortable": true,
|
||||||
|
"Showdown": true,
|
||||||
|
"Taggle": true,
|
||||||
|
"Gantt": true,
|
||||||
|
"Slick": true,
|
||||||
|
"Webcam": true,
|
||||||
|
"PhotoSwipe": true,
|
||||||
|
"PhotoSwipeUI_Default": true,
|
||||||
|
"io": true,
|
||||||
|
"JsBarcode": true,
|
||||||
|
"L": true,
|
||||||
|
"Chart": true,
|
||||||
|
"DataTable": true,
|
||||||
|
"Cypress": true,
|
||||||
|
"cy": true,
|
||||||
|
"it": true,
|
||||||
|
"describe": true,
|
||||||
|
"expect": true,
|
||||||
|
"context": true,
|
||||||
|
"before": true,
|
||||||
|
"beforeEach": true,
|
||||||
|
"after": true,
|
||||||
|
"qz": true,
|
||||||
|
"localforage": true,
|
||||||
|
"extend_cscript": true
|
||||||
|
}
|
||||||
|
}
|
||||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
.DS_Store
|
||||||
|
*.pyc
|
||||||
|
*.egg-info
|
||||||
|
*.swp
|
||||||
|
tags
|
||||||
|
node_modules
|
||||||
|
__pycache__
|
||||||
67
.pre-commit-config.yaml
Normal file
67
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
exclude: 'node_modules|.git'
|
||||||
|
default_stages: [commit]
|
||||||
|
fail_fast: false
|
||||||
|
|
||||||
|
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.3.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
files: "suppliers_portal.*"
|
||||||
|
exclude: ".*json$|.*txt$|.*csv|.*md|.*svg"
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-merge-conflict
|
||||||
|
- id: check-ast
|
||||||
|
- id: check-json
|
||||||
|
- id: check-toml
|
||||||
|
- id: check-yaml
|
||||||
|
- id: debug-statements
|
||||||
|
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
rev: v0.2.0
|
||||||
|
hooks:
|
||||||
|
- id: ruff
|
||||||
|
name: "Run ruff linter and apply fixes"
|
||||||
|
args: ["--fix"]
|
||||||
|
|
||||||
|
- id: ruff-format
|
||||||
|
name: "Format Python code"
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
|
rev: v2.7.1
|
||||||
|
hooks:
|
||||||
|
- id: prettier
|
||||||
|
types_or: [javascript, vue, scss]
|
||||||
|
# Ignore any files that might contain jinja / bundles
|
||||||
|
exclude: |
|
||||||
|
(?x)^(
|
||||||
|
suppliers_portal/public/dist/.*|
|
||||||
|
.*node_modules.*|
|
||||||
|
.*boilerplate.*|
|
||||||
|
suppliers_portal/templates/includes/.*|
|
||||||
|
suppliers_portal/public/js/lib/.*
|
||||||
|
)$
|
||||||
|
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-eslint
|
||||||
|
rev: v8.44.0
|
||||||
|
hooks:
|
||||||
|
- id: eslint
|
||||||
|
types_or: [javascript]
|
||||||
|
args: ['--quiet']
|
||||||
|
# Ignore any files that might contain jinja / bundles
|
||||||
|
exclude: |
|
||||||
|
(?x)^(
|
||||||
|
suppliers_portal/public/dist/.*|
|
||||||
|
cypress/.*|
|
||||||
|
.*node_modules.*|
|
||||||
|
.*boilerplate.*|
|
||||||
|
suppliers_portal/templates/includes/.*|
|
||||||
|
suppliers_portal/public/js/lib/.*
|
||||||
|
)$
|
||||||
|
|
||||||
|
ci:
|
||||||
|
autoupdate_schedule: weekly
|
||||||
|
skip: []
|
||||||
|
submodules: false
|
||||||
33
README.md
Normal file
33
README.md
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
### Suppliers Portal
|
||||||
|
|
||||||
|
Supplier Invoices, Supplier Quotes and Supplier Details
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
You can install this app using the [bench](https://github.com/frappe/bench) CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd $PATH_TO_YOUR_BENCH
|
||||||
|
bench get-app $URL_OF_THIS_REPO --branch develop
|
||||||
|
bench install-app suppliers_portal
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
|
||||||
|
This app uses `pre-commit` for code formatting and linting. Please [install pre-commit](https://pre-commit.com/#installation) and enable it for this repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd apps/suppliers_portal
|
||||||
|
pre-commit install
|
||||||
|
```
|
||||||
|
|
||||||
|
Pre-commit is configured to use the following tools for checking and formatting your code:
|
||||||
|
|
||||||
|
- ruff
|
||||||
|
- eslint
|
||||||
|
- prettier
|
||||||
|
- pyupgrade
|
||||||
|
|
||||||
|
### License
|
||||||
|
|
||||||
|
mit
|
||||||
5
frontend/.gitignore
vendored
Normal file
5
frontend/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
4
frontend/.prettierrc.json
Normal file
4
frontend/.prettierrc.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
42
frontend/README.md
Normal file
42
frontend/README.md
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Frappe UI Starter
|
||||||
|
|
||||||
|
This template should help get you started developing custom frontend for Frappe
|
||||||
|
apps with Vue 3 and the Frappe UI package.
|
||||||
|
|
||||||
|
This boilerplate sets up Vue 3, Vue Router, TailwindCSS, and Frappe UI out of
|
||||||
|
the box.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
This template is meant to be cloned inside an existing Frappe App. Assuming your
|
||||||
|
apps name is `todo`. Clone this template in the root folder of your app using `degit`.
|
||||||
|
|
||||||
|
```
|
||||||
|
cd apps/todo
|
||||||
|
npx degit netchampfaris/frappe-ui-starter frontend
|
||||||
|
cd frontend
|
||||||
|
yarn
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
In a development environment, you need to put the below key-value pair in your `site_config.json` file:
|
||||||
|
|
||||||
|
```
|
||||||
|
"ignore_csrf": 1
|
||||||
|
```
|
||||||
|
|
||||||
|
This will prevent `CSRFToken` errors while using the vite dev server. In production environment, the `csrf_token` is attached to the `window` object in `index.html` for you.
|
||||||
|
|
||||||
|
The Vite dev server will start on the port `8080`. This can be changed from `vite.config.js`.
|
||||||
|
The development server is configured to proxy your frappe app (usually running on port `8000`). If you have a site named `todo.test`, open `http://todo.test:8080` in your browser. If you see a button named "Click to send 'ping' request", congratulations!
|
||||||
|
|
||||||
|
If you notice the browser URL is `/frontend`, this is the base URL where your frontend app will run in production.
|
||||||
|
To change this, open `src/router.js` and change the base URL passed to `createWebHistory`.
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Vue 3](https://v3.vuejs.org/guide/introduction.html)
|
||||||
|
- [Vue Router](https://next.router.vuejs.org/guide/)
|
||||||
|
- [Frappe UI](https://github.com/frappe/frappe-ui)
|
||||||
|
- [TailwindCSS](https://tailwindcss.com/docs/utility-first)
|
||||||
|
- [Vite](https://vitejs.dev/guide/)
|
||||||
17
frontend/index.html
Normal file
17
frontend/index.html
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Supplier Portal</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<div id="modals"></div>
|
||||||
|
<div id="popovers"></div>
|
||||||
|
|
||||||
|
<script> window.csrf_token = '{{ csrf_token }}'; </script>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
26
frontend/package.json
Normal file
26
frontend/package.json
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "frappe-ui-frontend",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build --base=/assets/suppliers_portal/frontend/ && yarn copy-html-entry",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"copy-html-entry": "cp ../suppliers_portal/public/frontend/index.html ../suppliers_portal/www/frontend.html"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap": "5.2.3",
|
||||||
|
"bootstrap-icons": "^1.11.3",
|
||||||
|
"feather-icons": "^4.28.0",
|
||||||
|
"frappe-ui": "^0.1.69",
|
||||||
|
"vue": "^3.2.25",
|
||||||
|
"vue-router": "^4.0.12"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^2.0.0",
|
||||||
|
"autoprefixer": "^10.4.2",
|
||||||
|
"postcss": "^8.4.5",
|
||||||
|
"tailwindcss": "^3.0.15",
|
||||||
|
"vite": "^2.7.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
frontend/postcss.config.js
Normal file
6
frontend/postcss.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
BIN
frontend/public/favicon.png
Normal file
BIN
frontend/public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 440 B |
5
frontend/src/App.vue
Normal file
5
frontend/src/App.vue
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
BIN
frontend/src/assets/Inter/Inter-Black.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-Black.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Black.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-Black.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-BlackItalic.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-BlackItalic.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-BlackItalic.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-BlackItalic.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Bold.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-Bold.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Bold.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-Bold.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-BoldItalic.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-BoldItalic.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-BoldItalic.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-ExtraBold.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-ExtraBold.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-ExtraBold.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-ExtraBold.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-ExtraBoldItalic.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-ExtraBoldItalic.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-ExtraBoldItalic.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-ExtraBoldItalic.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-ExtraLight.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-ExtraLight.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-ExtraLight.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-ExtraLight.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-ExtraLightItalic.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-ExtraLightItalic.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-ExtraLightItalic.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-ExtraLightItalic.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Italic.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-Italic.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Italic.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-Italic.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Light.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-Light.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Light.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-Light.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-LightItalic.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-LightItalic.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-LightItalic.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-LightItalic.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Medium.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-Medium.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Medium.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-Medium.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-MediumItalic.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-MediumItalic.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-MediumItalic.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-MediumItalic.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Regular.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-Regular.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Regular.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-Regular.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-SemiBold.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-SemiBold.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-SemiBold.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-SemiBold.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-SemiBoldItalic.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-SemiBoldItalic.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-SemiBoldItalic.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-SemiBoldItalic.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Thin.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-Thin.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-Thin.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-Thin.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-ThinItalic.woff
Normal file
BIN
frontend/src/assets/Inter/Inter-ThinItalic.woff
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-ThinItalic.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-ThinItalic.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-italic.var.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-italic.var.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter-roman.var.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter-roman.var.woff2
Normal file
Binary file not shown.
BIN
frontend/src/assets/Inter/Inter.var.woff2
Normal file
BIN
frontend/src/assets/Inter/Inter.var.woff2
Normal file
Binary file not shown.
152
frontend/src/assets/Inter/inter.css
Normal file
152
frontend/src/assets/Inter/inter.css
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-Thin.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-Thin.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 100;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-ThinItalic.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-ThinItalic.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 200;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-ExtraLight.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-ExtraLight.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 200;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-ExtraLightItalic.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-ExtraLightItalic.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-Light.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-Light.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-LightItalic.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-LightItalic.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-Regular.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-Regular.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-Italic.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-Italic.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-Medium.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-Medium.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-MediumItalic.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-MediumItalic.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-SemiBold.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-SemiBold.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-SemiBoldItalic.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-SemiBoldItalic.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-Bold.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-Bold.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 700;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-BoldItalic.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-BoldItalic.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-ExtraBold.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-ExtraBold.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 800;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-ExtraBoldItalic.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-ExtraBoldItalic.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-Black.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-Black.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("Inter-BlackItalic.woff2?v=3.12") format("woff2"),
|
||||||
|
url("Inter-BlackItalic.woff?v=3.12") format("woff");
|
||||||
|
}
|
||||||
91
frontend/src/data/session.js
Normal file
91
frontend/src/data/session.js
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import router from '@/router'
|
||||||
|
import { computed, reactive } from 'vue'
|
||||||
|
import { createResource } from 'frappe-ui'
|
||||||
|
|
||||||
|
import { userResource } from './user'
|
||||||
|
|
||||||
|
export function sessionUser() {
|
||||||
|
const cookies = new URLSearchParams(document.cookie.split('; ').join('&'))
|
||||||
|
let _sessionUser = cookies.get('user_id')
|
||||||
|
if (_sessionUser === 'Guest') {
|
||||||
|
_sessionUser = null
|
||||||
|
}
|
||||||
|
return _sessionUser
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sessionSupplierId() {
|
||||||
|
const cookies = new URLSearchParams(document.cookie.split('; ').join('&'))
|
||||||
|
return cookies.get('supplier_id')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sessionSupplierName() {
|
||||||
|
const cookies = new URLSearchParams(document.cookie.split('; ').join('&'))
|
||||||
|
return cookies.get('supplier_name')
|
||||||
|
}
|
||||||
|
|
||||||
|
export const session = reactive({
|
||||||
|
supplier_login: createResource({
|
||||||
|
url: 'suppliers_portal.suppliers_portal.api.validate_supplier_id',
|
||||||
|
makeParams({ supplier_id }) {
|
||||||
|
return {
|
||||||
|
supplier_id,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess(data) {
|
||||||
|
if (data && data.status === 'success') {
|
||||||
|
userResource.reload()
|
||||||
|
session.user = sessionUser()
|
||||||
|
document.cookie = `supplier_id=${data.supplier_id}`
|
||||||
|
document.cookie = `supplier_name=${data.supplier_name}`
|
||||||
|
session.supplier_id = data.supplier_id
|
||||||
|
session.supplier_name = data.supplier_name
|
||||||
|
session.login.reset()
|
||||||
|
router.replace({ name: 'SupplierInvoiceList' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
console.error('Login failed', error)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
login: createResource({
|
||||||
|
url: 'login',
|
||||||
|
makeParams({ email, password }) {
|
||||||
|
return {
|
||||||
|
usr: email,
|
||||||
|
pwd: password,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSuccess(data) {
|
||||||
|
if (data) {
|
||||||
|
userResource.reload()
|
||||||
|
session.user = sessionUser()
|
||||||
|
// clear supplier_id and supplier_name
|
||||||
|
document.cookie = `supplier_id=${session.supplier_id}; expires=Thu, 01 Jan 1970 00:00:00 UTC'`;
|
||||||
|
document.cookie = `supplier_id=${session.supplier_name}; expires=Thu, 01 Jan 1970 00:00:00 UTC'`;
|
||||||
|
session.supplier_id = ''
|
||||||
|
session.supplier_name = ''
|
||||||
|
session.login.reset()
|
||||||
|
router.replace({ name: 'SupplierInvoiceList' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError(error) {
|
||||||
|
console.error('Login failed', error)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
logout: createResource({
|
||||||
|
url: 'logout',
|
||||||
|
onSuccess() {
|
||||||
|
document.cookie = `supplier_id=${session.supplier_id}; expires=Thu, 01 Jan 1970 00:00:00 UTC'`;
|
||||||
|
document.cookie = `supplier_id=${session.supplier_name}; expires=Thu, 01 Jan 1970 00:00:00 UTC'`;
|
||||||
|
session.supplier_id = ''
|
||||||
|
session.supplier_name = ''
|
||||||
|
userResource.reset()
|
||||||
|
session.user = sessionUser()
|
||||||
|
router.replace({ name: 'Login' })
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
user: sessionUser(),
|
||||||
|
supplier_id: sessionSupplierId(),
|
||||||
|
supplier_name: sessionSupplierName(),
|
||||||
|
isLoggedIn: computed(() => !!session.user),
|
||||||
|
})
|
||||||
12
frontend/src/data/user.js
Normal file
12
frontend/src/data/user.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import router from '@/router'
|
||||||
|
import { createResource } from 'frappe-ui'
|
||||||
|
|
||||||
|
export const userResource = createResource({
|
||||||
|
url: 'frappe.auth.get_logged_user',
|
||||||
|
cache: 'User',
|
||||||
|
onError(error) {
|
||||||
|
if (error && error.exc_type === 'AuthenticationError') {
|
||||||
|
router.push({ name: 'LoginPage' })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
22
frontend/src/data/utils.js
Normal file
22
frontend/src/data/utils.js
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
export const addDays = (date, days) => {
|
||||||
|
const result = new Date(date);
|
||||||
|
result.setDate(result.getDate() + days);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseInvoiceTerms = (invoiceTerm) => {
|
||||||
|
switch (invoiceTerm) {
|
||||||
|
case 'NET 10':
|
||||||
|
return 10;
|
||||||
|
case 'NET 15':
|
||||||
|
return 15;
|
||||||
|
case 'NET 30':
|
||||||
|
return 30;
|
||||||
|
case 'NET 45':
|
||||||
|
return 45;
|
||||||
|
case 'NET 60':
|
||||||
|
return 60;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
2
frontend/src/index.css
Normal file
2
frontend/src/index.css
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
@import './assets/Inter/inter.css';
|
||||||
|
@import 'frappe-ui/src/style.css';
|
||||||
32
frontend/src/main.js
Normal file
32
frontend/src/main.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
import { createApp } from 'vue'
|
||||||
|
import router from './router'
|
||||||
|
import App from './App.vue'
|
||||||
|
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||||
|
import 'bootstrap'
|
||||||
|
import 'bootstrap-icons/font/bootstrap-icons.css'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Input,
|
||||||
|
setConfig,
|
||||||
|
frappeRequest,
|
||||||
|
resourcesPlugin,
|
||||||
|
} from 'frappe-ui'
|
||||||
|
|
||||||
|
let app = createApp(App)
|
||||||
|
|
||||||
|
setConfig('resourceFetcher', frappeRequest)
|
||||||
|
|
||||||
|
app.use(router)
|
||||||
|
app.use(resourcesPlugin)
|
||||||
|
|
||||||
|
app.component('Button', Button)
|
||||||
|
app.component('Card', Card)
|
||||||
|
app.component('Input', Input)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
66
frontend/src/pages/Login.vue
Normal file
66
frontend/src/pages/Login.vue
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<template>
|
||||||
|
<div class="m-3 flex flex-row items-center justify-center">
|
||||||
|
<Card title="Welcome to Supplier Portal!" class="w-full max-w-md mt-4">
|
||||||
|
<form class="flex flex-col space-y-2 w-full" @submit.prevent="submit">
|
||||||
|
<Switch v-model="state.forAdmin" label="Login as Admin" />
|
||||||
|
<div v-if="state.forAdmin">
|
||||||
|
<Input
|
||||||
|
required
|
||||||
|
name="email"
|
||||||
|
type="text"
|
||||||
|
placeholder="username@email.com"
|
||||||
|
label="User ID"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
required
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="••••••"
|
||||||
|
label="Password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<Input
|
||||||
|
required
|
||||||
|
name="supplier_id"
|
||||||
|
type="text"
|
||||||
|
placeholder="Supplier ID"
|
||||||
|
label="Supplier ID"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Button :loading="session.login.loading" appearance="primary">Login</Button>
|
||||||
|
<div v-if="session.login.error" class="text-red-500">
|
||||||
|
{{ session.login.error }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import Switch from 'frappe-ui/src/components/Switch.vue'
|
||||||
|
import { reactive } from 'vue'
|
||||||
|
import { session } from '../data/session'
|
||||||
|
const state = reactive({
|
||||||
|
forAdmin: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (window.location.href.includes('accounting.seahavenind')) {
|
||||||
|
window.location.replace('https://accounting.seahavenind.com/erpnext')
|
||||||
|
console.log('redirected')
|
||||||
|
}
|
||||||
|
|
||||||
|
function submit(e) {
|
||||||
|
let formData = new FormData(e.target)
|
||||||
|
if (state.forAdmin) {
|
||||||
|
session.login.submit({
|
||||||
|
email: formData.get('email'),
|
||||||
|
password: formData.get('password'),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
session.supplier_login.submit({
|
||||||
|
supplier_id: formData.get('supplier_id'),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
597
frontend/src/pages/NewInvoice.vue
Normal file
597
frontend/src/pages/NewInvoice.vue
Normal file
|
|
@ -0,0 +1,597 @@
|
||||||
|
<template>
|
||||||
|
<form @submit.prevent="">
|
||||||
|
<div class="container mt-5 py-3 border">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-6">
|
||||||
|
<h1 class="text-gray-900 font-bold text-[32px]">New Invoice</h1>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="ms-auto">
|
||||||
|
<div class="btn-group" role="group" aria-label="Basic example">
|
||||||
|
<button type="button" class="btn btn-primary btn-light">
|
||||||
|
<router-link to="/">Cancel</router-link>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary btn-dark"
|
||||||
|
@click="saveInvoice"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container border-bottom pb-3">
|
||||||
|
<div class="mb-2 row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<label for="supplier_invoice_number" class="col-sm-3 col-form-label"
|
||||||
|
>Invoice Number</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="supplier_invoice_number"
|
||||||
|
v-model.number="invoiceData.supplier_invoice_number"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<label for="service_date" class="col-sm-3 col-form-label"
|
||||||
|
>Service Date</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<TextInput
|
||||||
|
:type="'date'"
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
placeholder="Service Date"
|
||||||
|
id="service_date"
|
||||||
|
:disabled="false"
|
||||||
|
v-model="invoiceData.service_date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2 row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<label for="supplier_id" class="col-sm-3 col-form-label"
|
||||||
|
>Supplier ID</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control-plaintext"
|
||||||
|
id="supplier_id"
|
||||||
|
v-model="invoiceData.supplier"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<label for="invoice_date" class="col-sm-3 col-form-label"
|
||||||
|
>Invoice Date</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<TextInput
|
||||||
|
:type="'date'"
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
placeholder="Invoice Date"
|
||||||
|
id="invoice_date"
|
||||||
|
:disabled="false"
|
||||||
|
v-model="invoiceData.invoice_date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2 row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<label for="supplier_name" class="col-sm-3 col-form-label"
|
||||||
|
>Supplier Name</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control-plaintext"
|
||||||
|
id="supplier_name"
|
||||||
|
:value="invoiceData.supplier_name"
|
||||||
|
v-if="!isAdministrator"
|
||||||
|
/>
|
||||||
|
<Autocomplete
|
||||||
|
id="supplier_name"
|
||||||
|
:options="portalSuppliers.data"
|
||||||
|
:placeholder="'Supplier Name'"
|
||||||
|
:hideSearch="invoiceData.supplier_invoice_number"
|
||||||
|
:label="'company_name'"
|
||||||
|
v-model="invoiceData.supplier_name"
|
||||||
|
v-else
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<label for="invoice_terms" class="col-sm-3 col-form-label"
|
||||||
|
>Invoice Terms</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<select
|
||||||
|
class="form-select form-select-mb h-100 d-inline-block"
|
||||||
|
aria-label="Invoice Terms"
|
||||||
|
v-model="invoiceData.invoice_terms"
|
||||||
|
>
|
||||||
|
<option readonly></option>
|
||||||
|
<option
|
||||||
|
v-for="option in invoiceTermOptions"
|
||||||
|
:value="option.value"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2 row">
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<label for="invoice_date" class="col-sm-3 col-form-label"
|
||||||
|
>Due Date</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<TextInput
|
||||||
|
:type="'date'"
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
placeholder="Due Date"
|
||||||
|
id="due_date"
|
||||||
|
:disabled="true"
|
||||||
|
v-model="invoiceDueDate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-2 row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="accordion accordion-flush" id="service-address-accordion">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header ps-0">
|
||||||
|
<button
|
||||||
|
class="accordion-button"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseOne"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="collapseOne"
|
||||||
|
>
|
||||||
|
Service Address
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
id="collapseOne"
|
||||||
|
class="accordion-collapse collapse show"
|
||||||
|
data-bs-parent="#service-address-accordion"
|
||||||
|
>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="street" class="col-sm-3 col-form-label"
|
||||||
|
>Street</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="street"
|
||||||
|
v-model="invoiceData.street"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="city" class="col-sm-3 col-form-label"
|
||||||
|
>City</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="city"
|
||||||
|
v-model="invoiceData.city"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="state" class="col-sm-3 col-form-label"
|
||||||
|
>State</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="state"
|
||||||
|
v-model="invoiceData.state"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="zip_code" class="col-sm-3 col-form-label"
|
||||||
|
>Zip Code</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="zip_code"
|
||||||
|
v-model="invoiceData.zip_code"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="accordion accordion-flush" id="address-accordion">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header ps-0">
|
||||||
|
<button
|
||||||
|
class="accordion-button"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapse-2"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="collapse-2"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
sessionSupplierId()
|
||||||
|
? 'Notes'
|
||||||
|
: 'Payment Reference and Notes'
|
||||||
|
}}
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
id="collapse-2"
|
||||||
|
class="accordion-collapse collapse show"
|
||||||
|
data-bs-parent="#address-accordion"
|
||||||
|
>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="row mb-1" v-if="!sessionSupplierId()">
|
||||||
|
<label
|
||||||
|
for="payment_reference_no"
|
||||||
|
class="col-sm-3 col-form-label"
|
||||||
|
>Payment Reference</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
id="payment_reference_no"
|
||||||
|
v-model="invoiceData.payment_reference_no"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1" v-if="!sessionSupplierId()">
|
||||||
|
<label
|
||||||
|
for="payment_reference_date"
|
||||||
|
class="col-sm-3 col-form-label"
|
||||||
|
>Payment Date</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<TextInput
|
||||||
|
:type="'date'"
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
placeholder="Payment Date"
|
||||||
|
id="payment_reference_date"
|
||||||
|
:disabled="false"
|
||||||
|
v-model="invoiceData.payment_reference_date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="notes" class="col-sm-3 col-form-label"
|
||||||
|
>Notes</label
|
||||||
|
>
|
||||||
|
<div class="col-md-8">
|
||||||
|
<Textarea
|
||||||
|
:variant="'subtle'"
|
||||||
|
size="md"
|
||||||
|
placeholder="Enter Notes Here"
|
||||||
|
:disabled="false"
|
||||||
|
v-model="invoiceData.notes"
|
||||||
|
id="notes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h4>Invoice item</h4>
|
||||||
|
<table class="table" v-if="invoiceItems.length !== 0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col" width="30%">Service Type</th>
|
||||||
|
<th scope="col">Quantity</th>
|
||||||
|
<th scope="col">Rate</th>
|
||||||
|
<th scope="col">Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="table-group-divider">
|
||||||
|
<tr v-for="(item, index) in invoiceItems" :key="index">
|
||||||
|
<th scope="row">{{ index + 1 }}</th>
|
||||||
|
<td>
|
||||||
|
<div class="col-mb-12">
|
||||||
|
<select
|
||||||
|
class="form-select form-select-mb h-100 d-inline-block"
|
||||||
|
aria-label="Service Types"
|
||||||
|
v-model="item.service_type"
|
||||||
|
>
|
||||||
|
<option readonly></option>
|
||||||
|
<option
|
||||||
|
v-for="option in serviceTypeOption"
|
||||||
|
:value="option.value"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input type="text" class="form-control" v-model="item.quantity" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"
|
||||||
|
><i class="bi bi-currency-dollar"></i>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
v-model.number="item.rate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"
|
||||||
|
><i class="bi bi-currency-dollar"></i>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control"
|
||||||
|
v-model.number="item.amount"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn btn-dark btn-sm">
|
||||||
|
<i @click="deleteRow(index)" class="bi bi-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div :class="invoiceItems.length === 0 ? 'ms-auto pt-3' : 'ms-auto'">
|
||||||
|
<div class="btn-group" role="group" aria-label="Basic example">
|
||||||
|
<button type="button" class="btn btn-dark btn-sm" @click="addRow">
|
||||||
|
<i class="bi bi-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="mb-2 row">
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<label for="total_amount" class="col-sm-3 col-form-label"
|
||||||
|
>Total Amount</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-5">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control-plaintext"
|
||||||
|
id="total_amount"
|
||||||
|
:value="invoiceData.total_amount"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, reactive, readonly, watch } from 'vue'
|
||||||
|
import { createListResource } from 'frappe-ui'
|
||||||
|
import { sessionSupplierId, sessionSupplierName, sessionUser } from '../data/session'
|
||||||
|
import { addDays, parseInvoiceTerms } from '../data/utils'
|
||||||
|
import router from '../router'
|
||||||
|
import TextInput from 'frappe-ui/src/components/TextInput.vue'
|
||||||
|
import Textarea from 'frappe-ui/src/components/Textarea.vue'
|
||||||
|
import Autocomplete from 'frappe-ui/src/components/Autocomplete.vue'
|
||||||
|
|
||||||
|
const isAdministrator = sessionUser() === 'Administrator'
|
||||||
|
|
||||||
|
const portalInvoice = createListResource({
|
||||||
|
doctype: 'Supplier Portal Invoices',
|
||||||
|
insert: {
|
||||||
|
onSuccess() {
|
||||||
|
router.push({ name: 'SupplierInvoiceList' })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const invoiceTermOptions = [
|
||||||
|
{
|
||||||
|
label: 'NET 10',
|
||||||
|
value: 'NET 10',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'NET 15',
|
||||||
|
value: 'NET 15',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'NET 30',
|
||||||
|
value: 'NET 30',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'NET 45',
|
||||||
|
value: 'NET 45',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'NET 60',
|
||||||
|
value: 'NET 60',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Due on Receipt',
|
||||||
|
value: 'Due on Receipt',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const serviceTypeOption = [
|
||||||
|
{
|
||||||
|
label: 'Repair',
|
||||||
|
value: 'Repair',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Preventive Maintenance',
|
||||||
|
value: 'Preventive Maintenance',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const invoiceData = reactive({
|
||||||
|
supplier_invoice_number: '',
|
||||||
|
supplier: sessionSupplierId(),
|
||||||
|
service_date: new Date().toISOString().split('T')[0],
|
||||||
|
invoice_date: new Date().toISOString().split('T')[0],
|
||||||
|
supplier_name: sessionSupplierName(),
|
||||||
|
invoice_terms: '',
|
||||||
|
total_amount: 0,
|
||||||
|
street: '',
|
||||||
|
city: '',
|
||||||
|
state: '',
|
||||||
|
zip_code: '',
|
||||||
|
payment_reference_no: '',
|
||||||
|
payment_reference_date: new Date().toISOString().split('T')[0],
|
||||||
|
notes: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const invoiceItems = reactive([])
|
||||||
|
let invoiceDueDate = computed(() => {
|
||||||
|
if (invoiceData.invoice_terms) {
|
||||||
|
let result = addDays(
|
||||||
|
invoiceData.invoice_date,
|
||||||
|
parseInvoiceTerms(invoiceData.invoice_terms),
|
||||||
|
)
|
||||||
|
// return as string
|
||||||
|
return result.toISOString().split('T')[0]
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const addRow = () => {
|
||||||
|
const rowObject = reactive({
|
||||||
|
service_type: '',
|
||||||
|
quantity: 0,
|
||||||
|
rate: 0,
|
||||||
|
amount: 0,
|
||||||
|
})
|
||||||
|
rowObject.amount = computed(() => rowObject.quantity * rowObject.rate)
|
||||||
|
invoiceItems.push(rowObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteRow = (index) => {
|
||||||
|
invoiceItems.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceData.total_amount = computed(() => {
|
||||||
|
return invoiceItems.reduce((acc, item) => acc + item.amount, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const saveInvoice = () => {
|
||||||
|
portalInvoice.insert.submit({
|
||||||
|
supplier_invoice_number: invoiceData.supplier_invoice_number,
|
||||||
|
supplier: invoiceData.supplier,
|
||||||
|
service_date: invoiceData.service_date,
|
||||||
|
invoice_date: invoiceData.invoice_date,
|
||||||
|
supplier_name: invoiceData.supplier_name,
|
||||||
|
invoice_terms: invoiceData.invoice_terms,
|
||||||
|
street: invoiceData.street,
|
||||||
|
city: invoiceData.city,
|
||||||
|
state: invoiceData.state,
|
||||||
|
zip_code: invoiceData.zip_code,
|
||||||
|
payment_reference_date: invoiceData.payment_reference_date,
|
||||||
|
payment_reference_no: invoiceData.payment_reference_no,
|
||||||
|
notes: invoiceData.notes,
|
||||||
|
supplier_invoice_items: invoiceItems.map((item) => {
|
||||||
|
return {
|
||||||
|
service_type: item.service_type,
|
||||||
|
quantity: item.quantity,
|
||||||
|
rate: item.rate,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const portalSuppliers = createListResource({
|
||||||
|
doctype: 'Portal Supplier',
|
||||||
|
fields: ['name as value', 'company_name as label'],
|
||||||
|
orderBy: 'creation desc',
|
||||||
|
})
|
||||||
|
portalSuppliers.fetch()
|
||||||
|
|
||||||
|
// watch for changes in invoiceData.supplier_name
|
||||||
|
watch(
|
||||||
|
() => invoiceData.supplier_name,
|
||||||
|
(newVal) => {
|
||||||
|
if (newVal) {
|
||||||
|
invoiceData.supplier = newVal.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
input.form-control-plaintext:focus {
|
||||||
|
background-color: #fff;
|
||||||
|
border-color: #fff;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
413
frontend/src/pages/SupplierInvoice.vue
Normal file
413
frontend/src/pages/SupplierInvoice.vue
Normal file
|
|
@ -0,0 +1,413 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="!invoice.loading && invoice.doc">
|
||||||
|
<div class="container mt-5 py-3 border">
|
||||||
|
<div class="row mb-3 vr-aligned">
|
||||||
|
<div class="col-6">
|
||||||
|
<h1 class="text-gray-900 font-bold text-[32px]">
|
||||||
|
Supplier Invoice: {{ invoice.doc.name }}
|
||||||
|
<Badge>
|
||||||
|
{{ invoice.doc.status }}
|
||||||
|
</Badge>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="ms-auto">
|
||||||
|
<div class="btn-group" role="group" aria-label="Basic example">
|
||||||
|
<button type="button" class="btn btn-primary btn-dark">
|
||||||
|
<router-link to="/">Back</router-link>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:class="
|
||||||
|
invoice.doc.supplier_invoice_items
|
||||||
|
? 'container border-bottom pb-3'
|
||||||
|
: 'container pb-3'
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="mb-1 row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<label for="suppplier_id" class="col-sm-3 col-form-label"
|
||||||
|
>Supplier ID</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control-plaintext"
|
||||||
|
id="suppplier_id"
|
||||||
|
:value="invoice.doc.supplier"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="service_date" class="col-sm-3 col-form-label"
|
||||||
|
>Service Date</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<TextInput
|
||||||
|
:type="'date'"
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
placeholder="Payment Date"
|
||||||
|
id="service_date"
|
||||||
|
:disabled="true"
|
||||||
|
v-model="invoice.doc.service_date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-1 row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<label for="suppplier_name" class="col-sm-3 col-form-label"
|
||||||
|
>Supplier Name</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control-plaintext"
|
||||||
|
id="suppplier_name"
|
||||||
|
:value="invoice.doc.supplier_name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="invoice_date" class="col-sm-3 col-form-label"
|
||||||
|
>Invoice Date</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<TextInput
|
||||||
|
:type="'date'"
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
placeholder="Invoice Date"
|
||||||
|
id="invoice_date"
|
||||||
|
:disabled="true"
|
||||||
|
v-model="invoice.doc.invoice_date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-1 row">
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="due_date" class="col-sm-3 col-form-label">Due Date</label>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<TextInput
|
||||||
|
:type="'date'"
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
placeholder="Due Date"
|
||||||
|
id="due_date"
|
||||||
|
:disabled="true"
|
||||||
|
v-model="invoice.doc.due_date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-1 row">
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<label for="invoice_terms" class="col-sm-3 col-form-label"
|
||||||
|
>Invoice Terms</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control-plaintext h-100 d-inline-block"
|
||||||
|
id="invoice_terms"
|
||||||
|
:value="invoice.doc.invoice_terms"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-1 row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="accordion accordion-flush" id="service-address-accordion">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header ps-0">
|
||||||
|
<button
|
||||||
|
class="accordion-button"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapseOne"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="collapseOne"
|
||||||
|
>
|
||||||
|
Service Address
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
id="collapseOne"
|
||||||
|
class="accordion-collapse collapse"
|
||||||
|
data-bs-parent="#service-address-accordion"
|
||||||
|
>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="street" class="col-sm-3 col-form-label"
|
||||||
|
>Street</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control-plaintext"
|
||||||
|
id="street"
|
||||||
|
:value="invoice.doc.street"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="city" class="col-sm-3 col-form-label"
|
||||||
|
>City</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control-plaintext"
|
||||||
|
id="city"
|
||||||
|
:value="invoice.doc.city"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="state" class="col-sm-3 col-form-label"
|
||||||
|
>State</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control-plaintext"
|
||||||
|
id="state"
|
||||||
|
:value="invoice.doc.state"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="zip_code" class="col-sm-3 col-form-label"
|
||||||
|
>Zip Code</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control-plaintext"
|
||||||
|
id="zip_code"
|
||||||
|
:value="invoice.doc.zip_code"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="accordion accordion-flush" id="address-accordion">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header ps-0">
|
||||||
|
<button
|
||||||
|
class="accordion-button"
|
||||||
|
type="button"
|
||||||
|
data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#collapse-2"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-controls="collapse-2"
|
||||||
|
>
|
||||||
|
Payment Reference and Notes
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
id="collapse-2"
|
||||||
|
class="accordion-collapse collapse"
|
||||||
|
data-bs-parent="#address-accordion"
|
||||||
|
>
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div
|
||||||
|
class="row mb-1"
|
||||||
|
v-if="invoice.doc.payment_reference_no"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
for="payment_reference_no"
|
||||||
|
class="col-sm-4 col-form-label"
|
||||||
|
>Payment Reference</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control-plaintext"
|
||||||
|
id="payment_reference_no"
|
||||||
|
:value="invoice.doc.payment_reference_no"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="row mb-1"
|
||||||
|
v-if="invoice.doc.payment_reference_date"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
for="payment_reference_date"
|
||||||
|
class="col-sm-4 col-form-label"
|
||||||
|
>Payment Date</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<TextInput
|
||||||
|
:type="'date'"
|
||||||
|
size="lg"
|
||||||
|
variant="outline"
|
||||||
|
placeholder="Payment Date"
|
||||||
|
id="payment_reference_date"
|
||||||
|
:disabled="true"
|
||||||
|
v-model="invoice.doc.payment_reference_date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1">
|
||||||
|
<label for="notes" class="col-sm-4 col-form-label"
|
||||||
|
>Notes</label
|
||||||
|
>
|
||||||
|
<div class="col-sm-8">
|
||||||
|
<Textarea
|
||||||
|
:variant="'subtle'"
|
||||||
|
size="md"
|
||||||
|
placeholder="Enter Notes Here"
|
||||||
|
:disabled="true"
|
||||||
|
v-model="invoice.doc.notes"
|
||||||
|
label=""
|
||||||
|
id="notes"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container" v-if="invoice.doc.supplier_invoice_items">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col" width="30%">Service Type</th>
|
||||||
|
<th scope="col">Quantity</th>
|
||||||
|
<th scope="col">Rate</th>
|
||||||
|
<th scope="col">Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="table-group-divider">
|
||||||
|
<tr
|
||||||
|
v-for="(item, index) in invoice.doc.supplier_invoice_items"
|
||||||
|
:key="item.name"
|
||||||
|
>
|
||||||
|
<th scope="row">{{ index + 1 }}</th>
|
||||||
|
<td>{{ item.service_type }}</td>
|
||||||
|
<td>{{ item.quantity }}</td>
|
||||||
|
<td><i class="bi bi-currency-dollar"></i> {{ item.rate }}</td>
|
||||||
|
<td><i class="bi bi-currency-dollar"></i> {{ item.amount }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="mb-1 row">
|
||||||
|
<div class="col"></div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="input-group">
|
||||||
|
<label for="total_amount" class="col-sm-3 col-form-label"
|
||||||
|
>Total Amount</label
|
||||||
|
>
|
||||||
|
<div class="cols-sm-5">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-text"
|
||||||
|
><i class="bi bi-currency-dollar"></i>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readonly
|
||||||
|
class="form-control"
|
||||||
|
v-model.number="invoice.doc.total_amount"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { createDocumentResource, createListResource, Badge } from 'frappe-ui'
|
||||||
|
import Textarea from 'frappe-ui/src/components/Textarea.vue'
|
||||||
|
import TextInput from 'frappe-ui/src/components/TextInput.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
supplierInvoiceNumber: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const invoice = createDocumentResource({
|
||||||
|
doctype: 'Supplier Portal Invoices',
|
||||||
|
name: props.supplierInvoiceNumber,
|
||||||
|
auto: true,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
.vr-aligned {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.accordion-button[aria-expanded='true'] {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.form-control-plaintext:focus,
|
||||||
|
.input-group > input.form-control:focus,
|
||||||
|
input.form-control,
|
||||||
|
.input-group > span {
|
||||||
|
background-color: #fff;
|
||||||
|
border-color: #fff;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled-date {
|
||||||
|
color: black;
|
||||||
|
background: transparent;
|
||||||
|
border: none !important;
|
||||||
|
border-bottom: 1px solid #ced4da !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
94
frontend/src/pages/SupplierInvoiceList.vue
Normal file
94
frontend/src/pages/SupplierInvoiceList.vue
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
<template>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="container mx-auto mt-5">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<h1 class="text-gray-900 font-bold text-[32px]">Supplier Invoices</h1>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div>
|
||||||
|
<div class="btn-group" role="group" aria-label="Basic example">
|
||||||
|
<button type="button" class="btn btn-sm btn-primary btn-dark"
|
||||||
|
@click="session.logout.submit">Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="ms-auto">
|
||||||
|
<div class="btn-group" role="group" aria-label="Basic example">
|
||||||
|
<router-link to="/invoices/new">
|
||||||
|
<button type="button" class="btn btn-primary btn-dark">New</button>
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container mx-auto mb-3 mt-5" v-if="invoice.data">
|
||||||
|
<table class=" table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Invoice Number</th>
|
||||||
|
<th scope="col">Supplier</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
<th scope="col">Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="table-group-divider">
|
||||||
|
<tr v-for="(item, index) in invoice.data" :key="index">
|
||||||
|
|
||||||
|
<td> <router-link
|
||||||
|
:to="{ name: 'SupplierInvoice', params: { supplierInvoiceNumber: item.name } }">{{
|
||||||
|
item.name }}</router-link>
|
||||||
|
</td>
|
||||||
|
<td>{{ item.supplier }}</td>
|
||||||
|
<td>
|
||||||
|
<Badge>{{ item.status }}</Badge>
|
||||||
|
</td>
|
||||||
|
<td><i class="bi bi-currency-dollar"></i> {{ item.total_amount }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between mt-4" v-if="invoice.length > 10">
|
||||||
|
<Button @click="invoice.previous()"> Previous Page </Button>
|
||||||
|
<Button @click="invoice.next()"> Next Page </Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { createListResource, Badge } from 'frappe-ui'
|
||||||
|
import { session } from '../data/session';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let invoiceParams = {
|
||||||
|
doctype: 'Supplier Portal Invoices',
|
||||||
|
fields: ['*'],
|
||||||
|
orderBy: 'creation desc',
|
||||||
|
start: 0,
|
||||||
|
pageLength: 5,
|
||||||
|
auto: true,
|
||||||
|
}
|
||||||
|
let invoice = null
|
||||||
|
|
||||||
|
if (session.supplier_id && session.isLoggedIn) {
|
||||||
|
invoiceParams.filters = [['supplier', '=', session.supplier_id]]
|
||||||
|
invoice = createListResource(invoiceParams)
|
||||||
|
} else {
|
||||||
|
invoice = createListResource(invoiceParams)
|
||||||
|
}
|
||||||
|
invoice.fetch()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="css" scoped>
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
0
frontend/src/pages/TableRow.vue
Normal file
0
frontend/src/pages/TableRow.vue
Normal file
51
frontend/src/router.js
Normal file
51
frontend/src/router.js
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import { session } from './data/session'
|
||||||
|
import { userResource } from '@/data/user'
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
name: 'Login',
|
||||||
|
path: '/account/login',
|
||||||
|
component: () => import('@/pages/Login.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SupplierInvoiceList',
|
||||||
|
path: '/',
|
||||||
|
component: () => import('@/pages/SupplierInvoiceList.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SupplierInvoice',
|
||||||
|
path: '/invoices/:supplierInvoiceNumber',
|
||||||
|
component: () => import('@/pages/SupplierInvoice.vue'),
|
||||||
|
props: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'SupplierInvoiceCreate',
|
||||||
|
path: '/invoices/new',
|
||||||
|
component: () => import('@/pages/NewInvoice.vue'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
let router = createRouter({
|
||||||
|
history: createWebHistory('/supplier-portal'),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
|
|
||||||
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
let isLoggedIn = session.isLoggedIn
|
||||||
|
try {
|
||||||
|
await userResource.promise
|
||||||
|
} catch (error) {
|
||||||
|
isLoggedIn = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to.name === 'Login' && isLoggedIn) {
|
||||||
|
next({ name: 'SupplierInvoiceList' })
|
||||||
|
} else if (to.name !== 'Login' && !isLoggedIn) {
|
||||||
|
next({ name: 'Login' })
|
||||||
|
} else {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export default router
|
||||||
18
frontend/tailwind.config.js
Normal file
18
frontend/tailwind.config.js
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
require('frappe-ui/src/utils/tailwind.config')
|
||||||
|
],
|
||||||
|
content: [
|
||||||
|
"./index.html",
|
||||||
|
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||||
|
"./node_modules/frappe-ui/src/components/**/*.{vue,js,ts,jsx,tsx}",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
35
frontend/vite.config.js
Normal file
35
frontend/vite.config.js
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from 'path'
|
||||||
|
import { webserver_port } from '../../../sites/common_site_config.json'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
server: {
|
||||||
|
port: 8000,
|
||||||
|
proxy: {
|
||||||
|
'^/(app|login|api|assets|files)': {
|
||||||
|
target: `http://127.0.0.1:${webserver_port}`,
|
||||||
|
ws: true,
|
||||||
|
router: function (req) {
|
||||||
|
const site_name = req.headers.host.split(':')[0]
|
||||||
|
return `http://${site_name}:${webserver_port}`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, 'src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: `../${path.basename(path.resolve('..'))}/public/frontend`,
|
||||||
|
emptyOutDir: true,
|
||||||
|
target: 'es2015',
|
||||||
|
},
|
||||||
|
optimizeDeps: {
|
||||||
|
include: ['frappe-ui > feather-icons', 'showdown', 'engine.io-client'],
|
||||||
|
},
|
||||||
|
})
|
||||||
1801
frontend/yarn.lock
Normal file
1801
frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
21
license.txt
Normal file
21
license.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) [year] [fullname]
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
55
pyproject.toml
Normal file
55
pyproject.toml
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
[project]
|
||||||
|
name = "suppliers_portal"
|
||||||
|
authors = [
|
||||||
|
{ name = "jeowsome15@gmail.com", email = "jeowsome15@gmail.com"}
|
||||||
|
]
|
||||||
|
description = "Supplier Invoices, Supplier Quotes and Supplier Details"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
readme = "README.md"
|
||||||
|
dynamic = ["version"]
|
||||||
|
dependencies = [
|
||||||
|
# "frappe~=15.0.0" # Installed and managed by bench.
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["flit_core >=3.4,<4"]
|
||||||
|
build-backend = "flit_core.buildapi"
|
||||||
|
|
||||||
|
# These dependencies are only installed when developer mode is enabled
|
||||||
|
[tool.bench.dev-dependencies]
|
||||||
|
# package_name = "~=1.1.0"
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 110
|
||||||
|
target-version = "py310"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = [
|
||||||
|
"F",
|
||||||
|
"E",
|
||||||
|
"W",
|
||||||
|
"I",
|
||||||
|
"UP",
|
||||||
|
"B",
|
||||||
|
]
|
||||||
|
ignore = [
|
||||||
|
"B017", # assertRaises(Exception) - should be more specific
|
||||||
|
"B018", # useless expression, not assigned to anything
|
||||||
|
"B023", # function doesn't bind loop variable - will have last iteration's value
|
||||||
|
"B904", # raise inside except without from
|
||||||
|
"E101", # indentation contains mixed spaces and tabs
|
||||||
|
"E402", # module level import not at top of file
|
||||||
|
"E501", # line too long
|
||||||
|
"E741", # ambiguous variable name
|
||||||
|
"F401", # "unused" imports
|
||||||
|
"F403", # can't detect undefined names from * import
|
||||||
|
"F405", # can't detect undefined names from * import
|
||||||
|
"F722", # syntax error in forward type annotation
|
||||||
|
"W191", # indentation contains tabs
|
||||||
|
]
|
||||||
|
typing-modules = ["frappe.types.DF"]
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
quote-style = "double"
|
||||||
|
indent-style = "tab"
|
||||||
|
docstring-code-format = true
|
||||||
1
suppliers_portal/__init__.py
Normal file
1
suppliers_portal/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
__version__ = "0.0.1"
|
||||||
0
suppliers_portal/config/__init__.py
Normal file
0
suppliers_portal/config/__init__.py
Normal file
249
suppliers_portal/hooks.py
Normal file
249
suppliers_portal/hooks.py
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
app_name = "suppliers_portal"
|
||||||
|
app_title = "Suppliers Portal"
|
||||||
|
app_publisher = "jeowsome15@gmail.com"
|
||||||
|
app_description = "Supplier Invoices, Supplier Quotes and Supplier Details"
|
||||||
|
app_email = "jeowsome15@gmail.com"
|
||||||
|
app_license = "mit"
|
||||||
|
|
||||||
|
# Apps
|
||||||
|
# ------------------
|
||||||
|
|
||||||
|
# required_apps = []
|
||||||
|
|
||||||
|
# Each item in the list will be shown as an app in the apps page
|
||||||
|
# add_to_apps_screen = [
|
||||||
|
# {
|
||||||
|
# "name": "suppliers_portal",
|
||||||
|
# "logo": "/assets/suppliers_portal/logo.png",
|
||||||
|
# "title": "Suppliers Portal",
|
||||||
|
# "route": "/suppliers_portal",
|
||||||
|
# "has_permission": "suppliers_portal.api.permission.has_app_permission"
|
||||||
|
# }
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# Includes in <head>
|
||||||
|
# ------------------
|
||||||
|
|
||||||
|
# include js, css files in header of desk.html
|
||||||
|
# app_include_css = "/assets/suppliers_portal/css/suppliers_portal.css"
|
||||||
|
# app_include_js = "/assets/suppliers_portal/js/suppliers_portal.js"
|
||||||
|
|
||||||
|
# include js, css files in header of web template
|
||||||
|
# web_include_css = "/assets/suppliers_portal/css/suppliers_portal.css"
|
||||||
|
# web_include_js = "/assets/suppliers_portal/js/suppliers_portal.js"
|
||||||
|
|
||||||
|
# include custom scss in every website theme (without file extension ".scss")
|
||||||
|
# website_theme_scss = "suppliers_portal/public/scss/website"
|
||||||
|
|
||||||
|
# include js, css files in header of web form
|
||||||
|
# webform_include_js = {"doctype": "public/js/doctype.js"}
|
||||||
|
# webform_include_css = {"doctype": "public/css/doctype.css"}
|
||||||
|
|
||||||
|
# include js in page
|
||||||
|
# page_js = {"page" : "public/js/file.js"}
|
||||||
|
|
||||||
|
# include js in doctype views
|
||||||
|
# doctype_js = {"doctype" : "public/js/doctype.js"}
|
||||||
|
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
|
||||||
|
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
|
||||||
|
# doctype_calendar_js = {"doctype" : "public/js/doctype_calendar.js"}
|
||||||
|
|
||||||
|
# Svg Icons
|
||||||
|
# ------------------
|
||||||
|
# include app icons in desk
|
||||||
|
# app_include_icons = "suppliers_portal/public/icons.svg"
|
||||||
|
|
||||||
|
# Home Pages
|
||||||
|
# ----------
|
||||||
|
|
||||||
|
# application home page (will override Website Settings)
|
||||||
|
# home_page = "login"
|
||||||
|
|
||||||
|
# website user home page (by Role)
|
||||||
|
# role_home_page = {
|
||||||
|
# "Role": "home_page"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Generators
|
||||||
|
# ----------
|
||||||
|
|
||||||
|
# automatically create page for each record of this doctype
|
||||||
|
# website_generators = ["Web Page"]
|
||||||
|
|
||||||
|
# Jinja
|
||||||
|
# ----------
|
||||||
|
|
||||||
|
# add methods and filters to jinja environment
|
||||||
|
# jinja = {
|
||||||
|
# "methods": "suppliers_portal.utils.jinja_methods",
|
||||||
|
# "filters": "suppliers_portal.utils.jinja_filters"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
# before_install = "suppliers_portal.install.before_install"
|
||||||
|
# after_install = "suppliers_portal.install.after_install"
|
||||||
|
|
||||||
|
# Uninstallation
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
# before_uninstall = "suppliers_portal.uninstall.before_uninstall"
|
||||||
|
# after_uninstall = "suppliers_portal.uninstall.after_uninstall"
|
||||||
|
|
||||||
|
# Integration Setup
|
||||||
|
# ------------------
|
||||||
|
# To set up dependencies/integrations with other apps
|
||||||
|
# Name of the app being installed is passed as an argument
|
||||||
|
|
||||||
|
# before_app_install = "suppliers_portal.utils.before_app_install"
|
||||||
|
# after_app_install = "suppliers_portal.utils.after_app_install"
|
||||||
|
|
||||||
|
# Integration Cleanup
|
||||||
|
# -------------------
|
||||||
|
# To clean up dependencies/integrations with other apps
|
||||||
|
# Name of the app being uninstalled is passed as an argument
|
||||||
|
|
||||||
|
# before_app_uninstall = "suppliers_portal.utils.before_app_uninstall"
|
||||||
|
# after_app_uninstall = "suppliers_portal.utils.after_app_uninstall"
|
||||||
|
|
||||||
|
# Desk Notifications
|
||||||
|
# ------------------
|
||||||
|
# See frappe.core.notifications.get_notification_config
|
||||||
|
|
||||||
|
# notification_config = "suppliers_portal.notifications.get_notification_config"
|
||||||
|
|
||||||
|
# Permissions
|
||||||
|
# -----------
|
||||||
|
# Permissions evaluated in scripted ways
|
||||||
|
|
||||||
|
# permission_query_conditions = {
|
||||||
|
# "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# has_permission = {
|
||||||
|
# "Event": "frappe.desk.doctype.event.event.has_permission",
|
||||||
|
# }
|
||||||
|
|
||||||
|
# DocType Class
|
||||||
|
# ---------------
|
||||||
|
# Override standard doctype classes
|
||||||
|
|
||||||
|
# override_doctype_class = {
|
||||||
|
# "ToDo": "custom_app.overrides.CustomToDo"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Document Events
|
||||||
|
# ---------------
|
||||||
|
# Hook on document methods and events
|
||||||
|
|
||||||
|
# doc_events = {
|
||||||
|
# "*": {
|
||||||
|
# "on_update": "method",
|
||||||
|
# "on_cancel": "method",
|
||||||
|
# "on_trash": "method"
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Scheduled Tasks
|
||||||
|
# ---------------
|
||||||
|
|
||||||
|
# scheduler_events = {
|
||||||
|
# "all": [
|
||||||
|
# "suppliers_portal.tasks.all"
|
||||||
|
# ],
|
||||||
|
# "daily": [
|
||||||
|
# "suppliers_portal.tasks.daily"
|
||||||
|
# ],
|
||||||
|
# "hourly": [
|
||||||
|
# "suppliers_portal.tasks.hourly"
|
||||||
|
# ],
|
||||||
|
# "weekly": [
|
||||||
|
# "suppliers_portal.tasks.weekly"
|
||||||
|
# ],
|
||||||
|
# "monthly": [
|
||||||
|
# "suppliers_portal.tasks.monthly"
|
||||||
|
# ],
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
# -------
|
||||||
|
|
||||||
|
# before_tests = "suppliers_portal.install.before_tests"
|
||||||
|
|
||||||
|
# Overriding Methods
|
||||||
|
# ------------------------------
|
||||||
|
#
|
||||||
|
# override_whitelisted_methods = {
|
||||||
|
# "frappe.desk.doctype.event.event.get_events": "suppliers_portal.event.get_events"
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# each overriding function accepts a `data` argument;
|
||||||
|
# generated from the base implementation of the doctype dashboard,
|
||||||
|
# along with any modifications made in other Frappe apps
|
||||||
|
# override_doctype_dashboards = {
|
||||||
|
# "Task": "suppliers_portal.task.get_dashboard_data"
|
||||||
|
# }
|
||||||
|
|
||||||
|
# exempt linked doctypes from being automatically cancelled
|
||||||
|
#
|
||||||
|
# auto_cancel_exempted_doctypes = ["Auto Repeat"]
|
||||||
|
|
||||||
|
# Ignore links to specified DocTypes when deleting documents
|
||||||
|
# -----------------------------------------------------------
|
||||||
|
|
||||||
|
# ignore_links_on_delete = ["Communication", "ToDo"]
|
||||||
|
|
||||||
|
# Request Events
|
||||||
|
# ----------------
|
||||||
|
# before_request = ["suppliers_portal.utils.before_request"]
|
||||||
|
# after_request = ["suppliers_portal.utils.after_request"]
|
||||||
|
|
||||||
|
# Job Events
|
||||||
|
# ----------
|
||||||
|
# before_job = ["suppliers_portal.utils.before_job"]
|
||||||
|
# after_job = ["suppliers_portal.utils.after_job"]
|
||||||
|
|
||||||
|
# User Data Protection
|
||||||
|
# --------------------
|
||||||
|
|
||||||
|
# user_data_fields = [
|
||||||
|
# {
|
||||||
|
# "doctype": "{doctype_1}",
|
||||||
|
# "filter_by": "{filter_by}",
|
||||||
|
# "redact_fields": ["{field_1}", "{field_2}"],
|
||||||
|
# "partial": 1,
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "doctype": "{doctype_2}",
|
||||||
|
# "filter_by": "{filter_by}",
|
||||||
|
# "partial": 1,
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "doctype": "{doctype_3}",
|
||||||
|
# "strict": False,
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "doctype": "{doctype_4}"
|
||||||
|
# }
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# Authentication and authorization
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
# auth_hooks = [
|
||||||
|
# "suppliers_portal.auth.validate"
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# Automatically update python controller files with type annotations for this app.
|
||||||
|
# export_python_type_annotations = True
|
||||||
|
|
||||||
|
# default_log_clearing_doctypes = {
|
||||||
|
# "Logging DocType Name": 30 # days to retain logs
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
|
website_route_rules = [
|
||||||
|
{"from_route": "/erpnext", "to_route": "/login"},
|
||||||
|
{"from_route": "/frontend/<path:app_path>", "to_route": "frontend"},
|
||||||
|
]
|
||||||
1
suppliers_portal/modules.txt
Normal file
1
suppliers_portal/modules.txt
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Suppliers Portal
|
||||||
6
suppliers_portal/patches.txt
Normal file
6
suppliers_portal/patches.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[pre_model_sync]
|
||||||
|
# Patches added in this section will be executed before doctypes are migrated
|
||||||
|
# Read docs to understand patches: https://frappeframework.com/docs/v14/user/en/database-migrations
|
||||||
|
|
||||||
|
[post_model_sync]
|
||||||
|
# Patches added in this section will be executed after doctypes are migrated
|
||||||
0
suppliers_portal/public/.gitkeep
Normal file
0
suppliers_portal/public/.gitkeep
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
import{_ as x,P as g,N as _,O as l,w as r,g as c,o,Q as p,R as C,S as k,m as a,e as d,y as u,T as m,F as h,E as b}from"./vendor.ea68c68e.js";const v={name:"FontColor",props:["editor"],components:{Popover:g,Tooltip:_},methods:{setBackgroundColor(t){t.name!="Default"?this.editor.chain().focus().toggleHighlight({color:t.hex}).run():this.editor.chain().focus().unsetHighlight().run()},setForegroundColor(t){t.name!="Default"?this.editor.chain().focus().setColor(t.hex).run():this.editor.chain().focus().unsetColor().run()}},computed:{foregroundColors(){return[{name:"Default",hex:"#1F272E"},{name:"Yellow",hex:"#ca8a04"},{name:"Orange",hex:"#ea580c"},{name:"Red",hex:"#dc2626"},{name:"Green",hex:"#16a34a"},{name:"Blue",hex:"#1579D0"},{name:"Purple",hex:"#9333ea"},{name:"Pink",hex:"#db2777"}]},backgroundColors(){return[{name:"Default",hex:null},{name:"Yellow",hex:"#fef9c3"},{name:"Orange",hex:"#ffedd5"},{name:"Red",hex:"#fee2e2"},{name:"Green",hex:"#dcfce7"},{name:"Blue",hex:"#D3E9FC"},{name:"Purple",hex:"#f3e8ff"},{name:"Pink",hex:"#fce7f3"}]}}},y={class:"p-2"},P=a("div",{class:"text-sm text-gray-700"},"Text Color",-1),B={class:"mt-1 grid grid-cols-8 gap-1"},F=["aria-label","onClick"],w=a("div",{class:"mt-2 text-sm text-gray-700"},"Background Color",-1),D={class:"mt-1 grid grid-cols-8 gap-1"},T=["aria-label","onClick"];function E(t,R,$,j,z,n){const i=c("Tooltip"),f=c("Popover");return o(),l(f,{transition:"default"},{target:r(({togglePopover:e,isOpen:s})=>[p(t.$slots,"default",C(k({onClick:()=>e(),isActive:s})))]),"body-main":r(()=>[a("div",y,[P,a("div",B,[(o(!0),d(h,null,u(n.foregroundColors,e=>(o(),l(i,{class:"flex",key:e.name,text:e.name},{default:r(()=>[a("button",{"aria-label":e.name,class:"flex h-5 w-5 items-center justify-center rounded border text-base",style:m({color:e.hex}),onClick:s=>n.setForegroundColor(e)}," A ",12,F)]),_:2},1032,["text"]))),128))]),w,a("div",D,[(o(!0),d(h,null,u(n.backgroundColors,e=>(o(),l(i,{class:"flex",key:e.name,text:e.name},{default:r(()=>[a("button",{"aria-label":e.name,class:b(["flex h-5 w-5 items-center justify-center rounded border text-base text-gray-900",e.hex?"border-transparent":"border-gray-200"]),style:m({backgroundColor:e.hex}),onClick:s=>n.setBackgroundColor(e)}," A ",14,T)]),_:2},1032,["text"]))),128))])])]),_:3})}var O=x(v,[["render",E]]);export{O as default};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{_ as f,j as I,V as _,X as h,e as g,Q as D,R as v,S as y,f as n,w as l,F as w,g as m,o as c,m as i,t as C,p as S,v as u}from"./vendor.ea68c68e.js";const V={name:"InsertImage",props:["editor"],expose:["openDialog"],data(){return{addImageDialog:{url:"",file:null,show:!1}}},components:{Button:I,Dialog:_},methods:{openDialog(){this.addImageDialog.show=!0},onImageSelect(t){let e=t.target.files[0];!e||(this.addImageDialog.file=e,h(e).then(r=>{this.addImageDialog.url=r}))},addImage(t){this.editor.chain().focus().setImage({src:t}).run(),this.reset()},reset(){this.addImageDialog=this.$options.data().addImageDialog}}},b={class:"relative cursor-pointer rounded-lg bg-gray-100 py-1 focus-within:bg-gray-200 hover:bg-gray-200"},k={class:"absolute inset-0 select-none px-2 py-1 text-base"},x=["src"],B=u(" Insert Image "),N=u(" Cancel ");function A(t,e,r,j,a,o){const d=m("Button"),p=m("Dialog");return c(),g(w,null,[D(t.$slots,"default",v(y({onClick:o.openDialog}))),n(p,{options:{title:"Add Image"},modelValue:a.addImageDialog.show,"onUpdate:modelValue":e[2]||(e[2]=s=>a.addImageDialog.show=s),onAfterLeave:o.reset},{"body-content":l(()=>[i("label",b,[i("input",{type:"file",class:"w-full opacity-0",onChange:e[0]||(e[0]=(...s)=>o.onImageSelect&&o.onImageSelect(...s)),accept:"image/*"},null,32),i("span",k,C(a.addImageDialog.file?"Select another image":"Select an image"),1)]),a.addImageDialog.url?(c(),g("img",{key:0,src:a.addImageDialog.url,class:"mt-2 w-full rounded-lg"},null,8,x)):S("",!0)]),actions:l(()=>[n(d,{variant:"solid",onClick:e[1]||(e[1]=s=>o.addImage(a.addImageDialog.url))},{default:l(()=>[B]),_:1}),n(d,{onClick:o.reset},{default:l(()=>[N]),_:1},8,["onClick"])]),_:1},8,["modelValue","onAfterLeave"])],64)}var L=f(V,[["render",A]]);export{L as default};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{_ as d,j as g,U as m,V as L,e as f,Q as p,R as D,S as c,f as i,w as l,F as h,g as a,o as _,W as v,v as w}from"./vendor.ea68c68e.js";const V={name:"InsertLink",props:["editor"],components:{Button:g,FormControl:m,Dialog:L},data(){return{setLinkDialog:{url:"",show:!1}}},methods:{openDialog(){let t=this.editor.getAttributes("link").href;t&&(this.setLinkDialog.url=t),this.setLinkDialog.show=!0},setLink(t){t===""?this.editor.chain().focus().extendMarkRange("link").unsetLink().run():this.editor.chain().focus().extendMarkRange("link").setLink({href:t}).run(),this.setLinkDialog.show=!1,this.setLinkDialog.url=""},reset(){this.setLinkDialog=this.$options.data().setLinkDialog}}},x=w(" Save ");function C(t,e,R,B,n,s){const r=a("FormControl"),u=a("Button"),k=a("Dialog");return _(),f(h,null,[p(t.$slots,"default",D(c({onClick:s.openDialog}))),i(k,{options:{title:"Set Link"},modelValue:n.setLinkDialog.show,"onUpdate:modelValue":e[3]||(e[3]=o=>n.setLinkDialog.show=o),onAfterLeave:s.reset},{"body-content":l(()=>[i(r,{type:"text",label:"URL",modelValue:n.setLinkDialog.url,"onUpdate:modelValue":e[0]||(e[0]=o=>n.setLinkDialog.url=o),onKeydown:e[1]||(e[1]=v(o=>s.setLink(o.target.value),["enter"]))},null,8,["modelValue"])]),actions:l(()=>[i(u,{variant:"solid",onClick:e[2]||(e[2]=o=>s.setLink(n.setLinkDialog.url))},{default:l(()=>[x]),_:1})]),_:1},8,["modelValue","onAfterLeave"])],64)}var U=d(V,[["render",C]]);export{U as default};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{_ as D,j as v,V as h,Y as C,e as u,Q as k,R as w,S as x,f as t,w as i,F as B,g as r,o as c,m as y,v as n,t as U,O as F,p}from"./vendor.ea68c68e.js";const S={name:"InsertImage",props:["editor"],expose:["openDialog"],data(){return{addVideoDialog:{url:"",file:null,show:!1}}},components:{Button:v,Dialog:h,FileUploader:C},methods:{openDialog(){this.addVideoDialog.show=!0},onVideoSelect(l){let o=l.target.files[0];!o||(this.addVideoDialog.file=o)},addVideo(l){this.editor.chain().focus().insertContent(`<video src="${l}"></video>`).run(),this.reset()},reset(){this.addVideoDialog=this.$options.data().addVideoDialog}}},I={class:"flex items-center space-x-2"},N=n(" Remove "),A=["src"],R=n(" Insert Video "),j=n("Cancel");function L(l,o,P,b,e,s){const a=r("Button"),V=r("FileUploader"),g=r("Dialog");return c(),u(B,null,[k(l.$slots,"default",w(x({onClick:s.openDialog}))),t(g,{options:{title:"Add Video"},modelValue:e.addVideoDialog.show,"onUpdate:modelValue":o[2]||(o[2]=d=>e.addVideoDialog.show=d),onAfterLeave:s.reset},{"body-content":i(()=>[t(V,{"file-types":"video/*",onSuccess:o[0]||(o[0]=d=>e.addVideoDialog.url=d.file_url)},{default:i(({file:d,progress:f,uploading:_,openFileSelector:m})=>[y("div",I,[t(a,{onClick:m},{default:i(()=>[n(U(_?`Uploading ${f}%`:e.addVideoDialog.url?"Change Video":"Upload Video"),1)]),_:2},1032,["onClick"]),e.addVideoDialog.url?(c(),F(a,{key:0,onClick:()=>{e.addVideoDialog.url=null,e.addVideoDialog.file=null}},{default:i(()=>[N]),_:2},1032,["onClick"])):p("",!0)])]),_:1}),e.addVideoDialog.url?(c(),u("video",{key:0,src:e.addVideoDialog.url,class:"mt-2 w-full rounded-lg",type:"video/mp4",controls:""},null,8,A)):p("",!0)]),actions:i(()=>[t(a,{variant:"solid",onClick:o[1]||(o[1]=d=>s.addVideo(e.addVideoDialog.url))},{default:i(()=>[R]),_:1}),t(a,{onClick:s.reset},{default:i(()=>[j]),_:1},8,["onClick"])]),_:1},8,["modelValue","onAfterLeave"])],64)}var E=D(S,[["render",L]]);export{E as default};
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
suppliers_portal/public/frontend/assets/Inter-Bold.2b828bef.woff
Normal file
BIN
suppliers_portal/public/frontend/assets/Inter-Bold.2b828bef.woff
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue