Legacy: final % = variant % + customer % − the largest active promotion on the product, its category or its brand; rial charge 0. The apps sent the variant's own % only, so the new app undercharged by the customer's percent (usually 3) and ignored promotions, and the server trusted whatever the app sent. - pricing/wage-formula.ts: one formula, legacy by default, with coefficients, promotion pick, constant, floor/cap and rounding so the admin screen can change it; validated - an app order is priced by it; the app's wage, discount and rial charge are ignored; an operator's manual numbers are kept - POST customer/me/wage-quote; both apps show that figure - admin/pricing/wage-formula (config.wage): read, preview, approve with the sample it was checked on, last 20 versions kept; the setting is kept off the generic const routes Owner approval: 2026-10-08. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
import { Body, Controller, Get, HttpCode, Post, Put } from '@nestjs/common';
|
|
import { ArrayMaxSize, IsArray, IsString } from 'class-validator';
|
|
import { CurrentAdmin, RequirePermission } from '../auth/guards';
|
|
import type { AuthenticatedAdmin } from '../auth/jwt.strategy';
|
|
import { CurrentCustomer, CustomerAuth } from '../customer-auth/customer.guards';
|
|
import type { AuthenticatedCustomer } from '../customer-auth/customer-jwt.strategy';
|
|
import { WageService } from './wage.service';
|
|
|
|
/**
|
|
* «اجرت» admin screen — the making-charge formula, a worked sample, and the
|
|
* approval that puts a new formula in force. Behind its own rid.
|
|
*/
|
|
@Controller('admin/pricing/wage-formula')
|
|
@RequirePermission('config.wage')
|
|
export class WageAdminController {
|
|
constructor(private readonly wage: WageService) {}
|
|
|
|
@Get()
|
|
get() {
|
|
return this.wage.getConfig();
|
|
}
|
|
|
|
/** Nothing is saved: the draft and the formula in force on the same sample. */
|
|
@Post('preview')
|
|
@HttpCode(200)
|
|
preview(@Body() body: { formula?: unknown; sample?: unknown }) {
|
|
return this.wage.preview(body?.formula, body?.sample);
|
|
}
|
|
|
|
/** Puts the formula in force, recorded with the sample it was checked on. */
|
|
@Put()
|
|
approve(@Body() body: { formula?: unknown; sample?: unknown }, @CurrentAdmin() admin: AuthenticatedAdmin) {
|
|
return this.wage.approve(body?.formula, body?.sample, admin);
|
|
}
|
|
}
|
|
|
|
class WageQuoteDto {
|
|
@IsArray()
|
|
@ArrayMaxSize(200)
|
|
@IsString({ each: true })
|
|
variantIds!: string[];
|
|
}
|
|
|
|
/**
|
|
* The making charge the app shows on a product and in the cart — computed by
|
|
* the same code that prices the order, for the signed-in customer.
|
|
*/
|
|
@Controller('customer/me/wage-quote')
|
|
export class WageCustomerController {
|
|
constructor(private readonly wage: WageService) {}
|
|
|
|
@CustomerAuth()
|
|
@Post()
|
|
@HttpCode(200)
|
|
async quote(@Body() dto: WageQuoteDto, @CurrentCustomer() me: AuthenticatedCustomer) {
|
|
return [...(await this.wage.quote(me.id, dto.variantIds)).values()];
|
|
}
|
|
}
|